骨传导完善
authorliangzhenying <867174124@qq.com>
Mon, 29 Dec 2025 08:57:08 +0000 (16:57 +0800)
committerliangzhenying <867174124@qq.com>
Mon, 29 Dec 2025 08:57:08 +0000 (16:57 +0800)
53 files changed:
DeepChordsSdk_iOS/DeepChordsSdk/Example/DeepChordsSdk/DCBleManager.swift
DeepChordsSdk_iOS/DeepChordsSdk/Example/DeepChordsSdk/DCBleRecord.swift
DeepChordsSdk_iOS/DeepChordsSdk/Example/DeepChordsSdk/DCChat.swift
DeepChordsSdk_iOS/DeepChordsSdk/Example/DeepChordsSdk/DCChinse2En.swift [new file with mode: 0644]
DeepChordsSdk_iOS/DeepChordsSdk/Example/DeepChordsSdk/DCPhoneRecord.swift
DeepChordsSdk_iOS/DeepChordsSdk/Example/Example/SceneDelegate.swift
DeepChordsSdk_iOS/DeepChordsSdk/Example/Example/VC/ChatVC.swift
DeepChordsSdk_iOS/DeepChordsSdk/Example/Example/VC/EarPhoneRecordVC.swift
DeepChordsSdk_iOS/DeepChordsSdk/Example/Example/tools/DCChatManager.swift
DeepChordsSdk_iOS/DeepChordsSdk/Example/Example/tools/DCSdkManager.swift
ShenYin/ShenYin.xcodeproj/project.pbxproj
ShenYin/ShenYin/Info.plist
ShenYin/ShenYin/Launch/AppLaunch.swift
ShenYin/ShenYin/Launch/AppManager.swift
ShenYin/ShenYin/Sdk/CCAiManager.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/CCBleManager.swift
ShenYin/ShenYin/Sdk/CCCall.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/CCPage.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/CCRole.swift
ShenYin/ShenYin/Sdk/CCSdkManager.swift [deleted file]
ShenYin/ShenYin/Sdk/DcSdk/DCChatVC.swift [deleted file]
ShenYin/ShenYin/Sdk/DcSdk/DcBleManager.swift [moved from ShenYin/ShenYin/Sdk/DcSdk/BleManager.swift with 85% similarity]
ShenYin/ShenYin/Sdk/DcSdk/DcChatManager.swift [moved from ShenYin/ShenYin/Sdk/DcSdk/DCChatManager.swift with 86% similarity]
ShenYin/ShenYin/Sdk/DcSdk/DcChinese2Enligsh.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/DcSdk/DcSdkManager.swift [moved from ShenYin/ShenYin/Sdk/DcSdk/DCSdkManager.swift with 56% similarity]
ShenYin/ShenYin/Sdk/DcSdk/DcTcty.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/DcSdk/DcThly.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/DcSdk/DcXcly.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/DcSdk/VC/DcCallingRecordVC.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/DcSdk/VC/DcChatVC.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/DcSdk/VC/DcChinese2EnglishVC.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/DcSdk/VC/DcDialogTranslateVC.swift [moved from ShenYin/ShenYin/UI/Home/Dialog/DialogTranlateVC.swift with 94% similarity]
ShenYin/ShenYin/Sdk/DcSdk/VC/DcStereoCallVC.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/DcSdk/VC/DcTctyVC.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/DcSdk/VC/DcXclyVC.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/JoySdk/JoyChat.swift
ShenYin/ShenYin/Sdk/JoySdk/JoyChinse2English.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/JoySdk/JoyDialog.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/JoySdk/JoySdkManager.swift
ShenYin/ShenYin/Sdk/JoySdk/JoyTcty.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/JoySdk/VC/JoyCallingRecordVC.swift [moved from ShenYin/ShenYin/UI/Home/CallingRecordVC.swift with 98% similarity]
ShenYin/ShenYin/Sdk/JoySdk/VC/JoyChatVC.swift [moved from ShenYin/ShenYin/UI/Home/Chat/ChatVC.swift with 88% similarity]
ShenYin/ShenYin/Sdk/JoySdk/VC/JoyChinese2EnglishVC.swift [moved from ShenYin/ShenYin/UI/Home/Chinese2English/Chinese2EnglishVC.swift with 51% similarity]
ShenYin/ShenYin/Sdk/JoySdk/VC/JoyDialogTranlateVC.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/JoySdk/VC/JoyStereoCallVC.swift [moved from ShenYin/ShenYin/UI/Home/StereoCallVC.swift with 99% similarity]
ShenYin/ShenYin/Sdk/JoySdk/VC/JoyTctyVC.swift [new file with mode: 0644]
ShenYin/ShenYin/Sdk/JoySdk/VC/JoyXclyVC.swift [moved from ShenYin/ShenYin/UI/Home/FieldRecording/FieldRecordingVC.swift with 97% similarity]
ShenYin/ShenYin/UI/Home/Chat/ChatNavigationBarItem.swift
ShenYin/ShenYin/UI/Home/Chat/RoleListVC.swift
ShenYin/ShenYin/UI/Home/RecordTranslateVC.swift [deleted file]
ShenYin/ShenYin/UI/Home/Root/HomeFunctionView.swift
ShenYin/ShenYin/UI/Home/Root/HomeHeadView.swift
ShenYin/ShenYin/UI/Home/TextTranslate/TextTranslateVC.swift

index 15b9722..21420f4 100644 (file)
@@ -125,13 +125,13 @@ extension DCBleManager{
                 DCLogger.debug("打开AI对话")
             case 0x02:
                 DCLogger.debug("关闭AI对话")
-                DCChat.shared.stopChat()
+//                DCChat.shared.stopChat()
             case 0x03:
                 DCLogger.debug("打开耳机录音")
-                DCBleRecord.shared.start()
+//                DCBleRecord.shared.start(type: 0x00)
             case 0x04:
                 DCLogger.debug("关闭耳机录音")
-                DCBleRecord.shared.stop()
+//                DCBleRecord.shared.stop()
             default:
                 break
             }
index 48ef04b..55820d3 100644 (file)
@@ -94,8 +94,8 @@ public class DCBleRecord {
         }
     }
     
-
-    public func start(){
+    /// 0x00实时录音,0x0c通话录音
+    public func start(type:Int){
         asrList = []
         asrText = ""
         
@@ -104,8 +104,7 @@ public class DCBleRecord {
         
         setupRecord()
         let config = StarburstBleAsrConfig()
-        config.type = 0x0c/// 0x00实时录音,0x0c通话录音
-        //开始录音
+        config.type = type
         StarburstSdk.shared().startDeviceRecord(config)
         
     }
index 34b409e..fdc5c88 100644 (file)
@@ -16,6 +16,17 @@ public class DCRole{
     public var type:String = ""
     /// 角色
     public var role:String = ""
+    
+    public init(){
+        
+    }
+    
+    public init(sid: String, name: String, type: String, role: String) {
+        self.sid = sid
+        self.name = name
+        self.type = type
+        self.role = role
+    }
 }
 
 public class DCChat{
diff --git a/DeepChordsSdk_iOS/DeepChordsSdk/Example/DeepChordsSdk/DCChinse2En.swift b/DeepChordsSdk_iOS/DeepChordsSdk/Example/DeepChordsSdk/DCChinse2En.swift
new file mode 100644 (file)
index 0000000..ba8b359
--- /dev/null
@@ -0,0 +1,87 @@
+//
+//  Untitled.swift
+//  Example
+//
+//  Created by liangshi on 2025/12/29.
+//
+
+import StarburstSdk
+
+public class DCChinse2En {
+    public static let shared = DCChinse2En()
+    /// 语⾔编码,默认为普通话(zh-CN),auto_lang自动识别
+    public var fromLanguage:String = "zh-CN"
+    /// ⽬标语⾔编码,默认为英语(en-US),auto_lang自动识别
+    public var toLanguage:String = "en-US"
+    /// 是否翻译,默认false。如果为true翻译源语言和目标语言不能是同一种语言
+    public var isTranslate = false
+    /// 是否进⾏Tts,默认false
+    public var isTTs = false
+    /// 默认pcm ⽀持pcm / ogg_opus / mp3
+    public var ttsFormat = "pcm"
+    /// 识别回调
+    public var asrCallback:((String,Int)->Void)?
+    /// 翻译回调
+    public var translateCallback:((String,Int)->Void)?
+    /// 翻译语音回调
+    public var translateDataCallback:((Data)->Void)?
+    /// 状态码回调
+    public var codeCallback:((Int)->Void)?
+    /// 打断
+    public var interruptionCallBack:(()->Void)?
+    /// 录音文件回调
+    public var filePathCallBack:((String?)->Void)?
+    
+    var asrList : [(String,Int)] = []
+    var asrText = ""
+    
+    var translateList : [(String,Int)] = []
+    var translateText = ""
+    
+    public func start(){
+        let config = StarburstAsrConfig()
+        config.isTranslate = isTranslate
+        config.fromLanguage = fromLanguage
+        config.toLanguage = toLanguage
+        config.isTTs = isTTs
+        config.ttsFormat = ttsFormat
+        asrList = []
+        asrText = ""
+        
+        translateList = []
+        translateText = ""
+        
+        StarburstSdk.shared().startRecordAsr(config) {voiceData in
+            //DCLogger.debug("录音的音频流 = \(voiceData!)")
+        } recognizeText: {[weak self] model in
+            guard let self = self else {return}
+            guard let model = model else {return}
+            
+            if let text = model.textResult,!text.isEmpty{
+//                DCLogger.debug("asr = \(text)")
+                asrCallback?(text,model.definiteIndex)
+            }
+            if let translate = model.translateResult,let text = translate.text,!text.isEmpty{
+//                DCLogger.debug("translate = \(text)")
+                translateCallback?(text,Int(translate.definiteIndex))
+            }
+            if let ttsData = model.ttsResult?.streamPiece {
+                if let data = Data(base64Encoded: ttsData){
+                    translateDataCallback?(data)
+                }
+            }
+        } stateChange: {[weak self] code in
+            DCLogger.debug("状态改变",code)
+            self?.codeCallback?(code.rawValue)
+        } interruption: { [weak self]  in
+            self?.interruptionCallBack?()
+        }
+    }
+    public func stop(){
+        StarburstSdk.shared().stopRecordAsr {[weak self] filePath in
+            DCLogger.debug("文件录音保存路径 \(filePath ?? "")")
+            self?.filePathCallBack?(filePath)
+        }
+        
+    }
+}
index 0d37199..6846441 100644 (file)
@@ -17,11 +17,13 @@ public class DCPhoneRecord {
     /// 是否进⾏Tts,默认false
     public var isTTs = false
     /// 默认pcm ⽀持pcm / ogg_opus / mp3
-    public var ttsFormat = "mp3"
+    public var ttsFormat = "pcm"
     /// 识别回调
     public var asrCallback:((String)->Void)?
-    /// 识别回调
+    /// 翻译回调
     public var translateCallback:((String)->Void)?
+    /// 翻译语音回调
+    public var translateDataCallback:((Data)->Void)?
     /// 状态码回调
     public var codeCallback:((Int)->Void)?
     /// 打断
@@ -86,6 +88,11 @@ public class DCPhoneRecord {
                 
                 translateCallback?(translateText)
             }
+            if let ttsData = model.ttsResult?.streamPiece {
+                if let data = Data(base64Encoded: ttsData){
+                    translateDataCallback?(data)
+                }
+            }
         } stateChange: {[weak self] code in
             DCLogger.debug("状态改变",code)
             self?.codeCallback?(code.rawValue)
index 5c01cf8..17a75bf 100644 (file)
@@ -19,7 +19,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
         guard let _ = (scene as? UIWindowScene) else { return }
         App.shared.window = window
         window?.rootViewController = UINavigationController(rootViewController: DeviceListVC())
-        DCSdkManager.shared.initSdk()
+        DcSdkManager.shared.initSdk()
     }
 
     func sceneDidDisconnect(_ scene: UIScene) {
index 096d47c..d7985be 100644 (file)
@@ -55,14 +55,14 @@ class ChatVC: BaseVC {
         }
     }
     func setupSDk(){
-        DCChatManager.shared.chatNeedReloadData = {[weak self]  in
+        DcChatManager.shared.chatNeedReloadData = {[weak self]  in
             guard let self = self else{return}
             tableView.reloadData()
-            if DCChatManager.shared.dataList.count > 0 {
-                tableView.scrollToRow(at: IndexPath(row: DCChatManager.shared.dataList.count - 1, section: 0), at: .none, animated: true)
+            if DcChatManager.shared.dataList.count > 0 {
+                tableView.scrollToRow(at: IndexPath(row: DcChatManager.shared.dataList.count - 1, section: 0), at: .none, animated: true)
             }
         }
-        DCChatManager.shared.codeCallback = { [weak self] code in
+        DcChatManager.shared.codeCallback = { [weak self] code in
             guard let self = self else{return}
             if code == 20001 {
                 self.recordBtn.isSelected = true
@@ -72,7 +72,7 @@ class ChatVC: BaseVC {
             }
             
         }
-        DCChatManager.shared.finishCallback = { [weak self] path in
+        DcChatManager.shared.finishCallback = { [weak self] path in
             guard let self = self else{return}
             //            self.recordBtn.isSelected = false
         }
@@ -83,19 +83,19 @@ class ChatVC: BaseVC {
     @objc func recordAction() {
         self.recordBtn.isSelected = !self.recordBtn.isSelected
         if self.recordBtn.isSelected {
-            DCChatManager.shared.startChat()
+            DcChatManager.shared.startChat()
         }
         else{
             stopAction()
         }
     }
     func stopAction() {
-        DCChatManager.shared.stopChat()
+        DcChatManager.shared.stopChat()
     }
     @objc func roleAction(){
         let vc = RoleVC()
         vc.didSelectedCallback = { role in
-            DCChatManager.shared.role = role
+            DcChatManager.shared.role = role
         }
         navigationController?.pushViewController(vc, animated: true)
     }
@@ -103,12 +103,12 @@ class ChatVC: BaseVC {
 
 extension ChatVC:UITableViewDelegate,UITableViewDataSource {
     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-        return DCChatManager.shared.dataList.count
+        return DcChatManager.shared.dataList.count
     }
     
     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
         let cell = tableView.dequeueReusableCell(withIdentifier:  ChatCell.reuseID, for: indexPath) as? ChatCell
-        cell!.data = DCChatManager.shared.dataList[indexPath.row]
+        cell!.data = DcChatManager.shared.dataList[indexPath.row]
         return cell!
     }
     func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
index ce25d9e..6890241 100644 (file)
@@ -134,7 +134,7 @@ extension EarPhoneRecordVC{
         DCBleRecord.shared.fromLanguage = fromLanguage
         DCBleRecord.shared.toLanguage = toLanguage
         DCBleRecord.shared.isTranslate = true
-        DCBleRecord.shared.start()
+        DCBleRecord.shared.start(type: 0x00)
     }
     
     func stopAction(){
index e2443d0..3e5b65c 100644 (file)
@@ -1,5 +1,5 @@
 //
-//  DCChatManager.swift
+//  DcChatManager.swift
 //  Example
 //
 //  Created by liangshi on 2025/12/15.
@@ -41,8 +41,8 @@ public class ChatCellModel:NSObject,NSCoding  {
 }
 
 
-class DCChatManager:NSObject{
-    static let shared = DCChatManager()
+class DcChatManager:NSObject{
+    static let shared = DcChatManager()
     private var isTtsPlaying = false
     private var audioData: Data?
     
@@ -176,7 +176,7 @@ class DCChatManager:NSObject{
 //        }
 //    }
 }
-extension DCChatManager : PcmSoundDelegate {
+extension DcChatManager : PcmSoundDelegate {
     func remaining(_ frames: UInt) {
         DCLogger.debug("[frame:\(frames)]");
         if (frames == 0) {
index 0e39f84..3a82873 100644 (file)
@@ -1,19 +1,18 @@
 //
-//  DCSdkManager.swift
+//  DcSdkManager.swift
 //  Example
 //
 //  Created by liangshi on 2025/12/19.
 //
 import DeepChordsSdk
 
-class DCSdkManager{
-    static let shared = DCSdkManager()
+class DcSdkManager{
+    static let shared = DcSdkManager()
     
     
     func initSdk(){
         DeepChordsAiSdk.shared.initSDK()
-//        DCRecordManager.shared.setupRecord()
-        DCChatManager.shared.setupChat()
+        DcChatManager.shared.setupChat()
         
         DCDevice.shared.eventCallback = { event in
             switch event {
index 1ea9a02..a2b618a 100644 (file)
                                GENERATE_INFOPLIST_FILE = YES;
                                INFOPLIST_FILE = ShenYin/Info.plist;
                                INFOPLIST_KEY_CFBundleDisplayName = DeepChords;
+                               INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
                                INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "扫描附近蓝牙设备需要获得权限";
                                INFOPLIST_KEY_NSCameraUsageDescription = "修改用户头像、提交意见反馈需要访问相册使用摄像头";
+                               INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "允许APP使用定位方便查找耳机";
                                INFOPLIST_KEY_NSMicrophoneUsageDescription = "录音需要使用麦克风";
                                INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "修改用户头像、提交意见反馈需要访问相册";
                                INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
                                INFOPLIST_KEY_UIMainStoryboardFile = Main;
                                INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = UIInterfaceOrientationPortrait;
                                INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
+                               INFOPLIST_KEY_UIUserInterfaceStyle = Light;
                                IPHONEOS_DEPLOYMENT_TARGET = 13.0;
                                LD_RUNPATH_SEARCH_PATHS = (
                                        "$(inherited)",
                                        "@executable_path/Frameworks",
                                );
                                MARKETING_VERSION = 1.0.2;
-                               PRODUCT_BUNDLE_IDENTIFIER = com.deepchords.app;
+                               PRODUCT_BUNDLE_IDENTIFIER = com.deepchords.app1;
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                PROVISIONING_PROFILE_SPECIFIER = "";
                                SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
                                GENERATE_INFOPLIST_FILE = YES;
                                INFOPLIST_FILE = ShenYin/Info.plist;
                                INFOPLIST_KEY_CFBundleDisplayName = DeepChords;
+                               INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
                                INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "扫描附近蓝牙设备需要获得权限";
                                INFOPLIST_KEY_NSCameraUsageDescription = "修改用户头像、提交意见反馈需要访问相册使用摄像头";
+                               INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "允许APP使用定位方便查找耳机";
                                INFOPLIST_KEY_NSMicrophoneUsageDescription = "录音需要使用麦克风";
                                INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "修改用户头像、提交意见反馈需要访问相册";
                                INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
                                INFOPLIST_KEY_UIMainStoryboardFile = Main;
                                INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = UIInterfaceOrientationPortrait;
                                INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
+                               INFOPLIST_KEY_UIUserInterfaceStyle = Light;
                                IPHONEOS_DEPLOYMENT_TARGET = 13.0;
                                LD_RUNPATH_SEARCH_PATHS = (
                                        "$(inherited)",
                                        "@executable_path/Frameworks",
                                );
                                MARKETING_VERSION = 1.0.2;
-                               PRODUCT_BUNDLE_IDENTIFIER = com.deepchords.app;
+                               PRODUCT_BUNDLE_IDENTIFIER = com.deepchords.app1;
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                PROVISIONING_PROFILE_SPECIFIER = "";
                                SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
index 16b05a5..82a0f3a 100644 (file)
                <string>bluetooth-central</string>
                <string>audio</string>
        </array>
-       <key>NSLocationWhenInUseUsageDescription</key>
-       <string>允许APP使用定位方便查找耳机</string>
-       <key>ITSAppUsesNonExemptEncryption</key>
-       <false/>
-       <key>UIUserInterfaceStyle</key>
-       <string>Light</string>
 </dict>
 </plist>
index 3d52d87..a4b1540 100644 (file)
@@ -15,7 +15,7 @@ class AppLaunch:NSObject{
 
         
         IASignWithWeChat.shared.register()
-        
+        CCCall.shared.start()
         setupKeyboard()
         loadCache()
         LoginVM.fetchCountryList {result in
@@ -39,6 +39,5 @@ class AppLaunch:NSObject{
         else{
             App.shared.goToLogin()
         }
-//        App.shared.window?.rootViewController = ChatVC()
     }
 }
index f86626e..1f3ae04 100644 (file)
@@ -9,8 +9,6 @@ class App{
     static let shared = App()
     var window:UIWindow?
     var isInBackground = false
-    var isJoy = true
-
     
     func topVC()->UIViewController!{
         guard let vc = window?.rootViewController else{
@@ -19,20 +17,7 @@ class App{
         }
         return getTopViewController(from: vc)
     }
-    func preparePlay(){
-        PcmSound.sharedInstance().stopAudioUnit()
-        PcmSound.sharedInstance().cleanup()
-        PcmSound.sharedInstance().setup(16000, withchannels: 1)
-        
-        let options: AVAudioSession.CategoryOptions = [.allowBluetooth]
-        do {
-            try AVAudioSession.sharedInstance().setCategory(.playback, options: [options])
-            try AVAudioSession.sharedInstance().setActive(true)
-        } catch {
-            Logger.debug(error)
-        }
-    }
-    
+
     func willLogin(){
         CacheManager.shared.user.tokenIsValid {[weak self] success in
             guard let self = self else{return}
@@ -46,11 +31,11 @@ class App{
         Logger.debug(DCFolder.user)
         Logger.debug(DCFolder.record)
         self.window?.rootViewController = BaseTC()
-        if App.shared.isJoy {
+        if CCAiManager.isJoy {
             JoySdkManager.shared.setupSdk()
         }
         else{
-            DCSdkManager.shared.initSdk()
+            DcSdkManager.shared.initSdk()
         }
     }
     func goToLogin(){
diff --git a/ShenYin/ShenYin/Sdk/CCAiManager.swift b/ShenYin/ShenYin/Sdk/CCAiManager.swift
new file mode 100644 (file)
index 0000000..518bb2d
--- /dev/null
@@ -0,0 +1,62 @@
+//
+//  CCAiManager.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/12/25.
+//
+import JoyGemAI
+import DeepChordsSdk
+
+class CCAiManager {
+    static var isJoy = true
+    
+    static func translate(text:String,from:String,to:String,finished:@escaping ((stream:String,finish:Bool)?)->Void){
+        
+        if isJoy {
+            JoyGemAIManager.shared().translate(text, from: from, to: to) { result, finish in
+                finished((result,finish))
+            } error: { error in
+                finished(nil)
+            }
+        }
+        else{
+            
+        }
+    }
+}
+extension CCAiManager{
+    static func preparePlayBlueTooth(){
+        PcmSound.sharedInstance().stopAudioUnit()
+        PcmSound.sharedInstance().cleanup()
+        PcmSound.sharedInstance().setup(16000, withchannels: 1)
+        
+        let options: AVAudioSession.CategoryOptions = [.allowBluetooth]
+        do {
+            try AVAudioSession.sharedInstance().setCategory(.playback, options: [options])
+            try AVAudioSession.sharedInstance().setActive(true)
+        } catch {
+            Logger.debug(error)
+        }
+    }
+//    static func preparePlay(){
+//        PcmSound.sharedInstance().stopAudioUnit()
+//        PcmSound.sharedInstance().cleanup()
+//        PcmSound.sharedInstance().setup(16000, withchannels: 1)
+//        
+//        do {
+//            try AVAudioSession.sharedInstance().setCategory(.playback)
+//            try AVAudioSession.sharedInstance().setActive(true)
+//        } catch {
+//            Logger.debug(error)
+//        }
+//    }
+    
+    static func stopPlay(){
+        PcmSound.sharedInstance().stopAudioUnit()
+        PcmSound.sharedInstance().cleanup()
+    }
+}
+
+
+
+
index 4e13a15..1f62adc 100644 (file)
@@ -71,13 +71,13 @@ extension CCBleManager:CBCentralManagerDelegate{
         }
         Logger.debug(peripheral)
         
-        if App.shared.isJoy {
+        if CCAiManager.isJoy{
             let mac = JoySdkManager.getMac(advertisementData) ?? ""
             devices.append(DCPeripheral(device: peripheral,mac: mac))
             discoverDeviceCallback?(devices)
         }
         else{
-            let mac = DCSdkManager.getMac(advertisementData) ?? ""
+            let mac = DcSdkManager.getMac(advertisementData) ?? ""
             devices.append(DCPeripheral(device: peripheral,mac: mac))
             discoverDeviceCallback?(devices)
         }
@@ -87,7 +87,7 @@ extension CCBleManager:CBCentralManagerDelegate{
         stopScanDevice()
         Logger.debug("蓝牙已连接 \(peripheral)")
         peripheral.delegate = self
-        if App.shared.isJoy {
+        if CCAiManager.isJoy{
             peripheral.discoverServices([CBUUID(string: JoySdkManager.serviceUUID),CBUUID(string: OtaMananger.otaServiceUUId)]);
         }
         else{
@@ -101,8 +101,9 @@ extension CCBleManager:CBCentralManagerDelegate{
     }
     func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
         Logger.debug("蓝牙已断开 \(peripheral)")
+        YLprogressHUD.showAutoHiden("蓝牙已断开".l10n)
         self.isConnetd = false
-        if App.shared.isJoy {
+        if CCAiManager.isJoy{
             DCBleManager.shared.centralManager(central, didDisconnectPeripheral: peripheral, error: nil)
         }
         NotificationManager.post(type: .deviceValidChange, objtect: nil)
@@ -121,7 +122,7 @@ extension CCBleManager: CBPeripheralDelegate {
     }
     
     func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
-        if App.shared.isJoy {
+        if CCAiManager.isJoy{
             if (service.uuid.uuidString == JoySdkManager.serviceUUID) {
                 JoySdkManager.shared.peripheral(peripheral, didDiscoverCharacteristicsFor: service, error: error)
             }
@@ -135,7 +136,7 @@ extension CCBleManager: CBPeripheralDelegate {
     }
     
     func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: (any Error)?) {
-        if App.shared.isJoy {
+        if CCAiManager.isJoy{
             if (characteristic.isNotifying == true && characteristic.service?.uuid.uuidString == JoySdkManager.serviceUUID && characteristic.uuid.uuidString == JoySdkManager.notifyUUID) {
                 Logger.debug("特征使能成功")
                 JoySdkManager.shared.bleConnected()
@@ -148,7 +149,7 @@ extension CCBleManager: CBPeripheralDelegate {
     
     func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
         //        Logger.debug("收到数据 ===uUId ",characteristic.uuid)
-        if App.shared.isJoy {
+        if CCAiManager.isJoy{
             if OtaMananger.isOtadata(characteristic){
                 if let _ = characteristic.value,error == nil {
                     OtaMananger.shared.otaManager.peripheral(peripheral, didUpdateValueFor: characteristic)
@@ -189,7 +190,7 @@ extension CCBleManager{
         //                devices.append(DCPeripheral(device: item,mac: mac))
         //            }
         //        }
-        if App.shared.isJoy {
+        if CCAiManager.isJoy{
             centralManager.scanForPeripherals(withServices: [CBUUID(string: JoySdkManager.serviceUUID)],options: [CBCentralManagerScanOptionAllowDuplicatesKey : false])
         }
         else{
diff --git a/ShenYin/ShenYin/Sdk/CCCall.swift b/ShenYin/ShenYin/Sdk/CCCall.swift
new file mode 100644 (file)
index 0000000..fc9d089
--- /dev/null
@@ -0,0 +1,54 @@
+//
+//  CCCall.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/12/29.
+//
+import CallKit
+import CoreTelephony
+
+class CCCall: NSObject, CXCallObserverDelegate {
+    static let shared = CCCall()
+    
+    let callObserver = CXCallObserver()
+    var isInCall = false
+    
+    override init() {
+        super.init()
+        checkInitialCallState()
+        start()
+    }
+    
+    func start() {
+        callObserver.setDelegate(self, queue: nil)
+    }
+    
+    func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
+        if call.hasEnded {
+            print("通话结束")
+            isInCall = false
+        } else if call.isOnHold {
+            print("通话保持中")
+            isInCall = true
+        } else if call.isOutgoing {
+            print("拨出电话")
+            isInCall = true
+        } else if call.hasConnected {
+            print("通话已接通")
+            isInCall = true
+        }
+    }
+    
+    private func checkInitialCallState() {
+        let callCenter = CTCallCenter()
+        if let calls = callCenter.currentCalls {
+            for call in calls {
+                if call.callState == CTCallStateDialing || call.callState == CTCallStateIncoming || call.callState == CTCallStateConnected {
+                    print("正在听话。。。")
+                    isInCall = true
+                    return
+                }
+            }
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/Sdk/CCPage.swift b/ShenYin/ShenYin/Sdk/CCPage.swift
new file mode 100644 (file)
index 0000000..75cc017
--- /dev/null
@@ -0,0 +1,67 @@
+//
+//  CCVCManager.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/12/26.
+//
+
+class CCPage{
+    static func gotoChat(){
+        if CCAiManager.isJoy {
+            App.shared.goToPage(JoyChatVC())
+        }
+        else{
+            App.shared.goToPage(DcChatVC())
+        }
+    }
+    static func gotoFieldRecord(){
+        if CCAiManager.isJoy {
+            App.shared.goToPage(JoyXclyVC())
+        }
+        else{
+            App.shared.goToPage(DcXclyVC())
+        }
+    }
+    static func gotoCallRecord(){
+        if CCAiManager.isJoy {
+            if JoySdkManager.shared.callRecord.supportStereo{
+                App.shared.goToPage(JoyStereoCallVC())
+            }
+            else{
+                App.shared.goToPage(JoyCallingRecordVC())
+            }
+        }
+        else{
+//            if JoySdkManager.shared.callRecord.supportStereo{
+//                App.shared.goToPage(JoyStereoCallVC())
+//            }
+//            else{
+                App.shared.goToPage(DcCallingRecordVC())
+//            }
+        }
+    }
+    static func gotoChinese2Enlgish(){
+        if CCAiManager.isJoy {
+            App.shared.goToPage(JoyChinese2EnglishVC())
+        }
+        else{
+            App.shared.goToPage(DcChinese2EnglishVC())
+        }
+    }
+    static func gotoTcty(){
+        if CCAiManager.isJoy {
+            App.shared.goToPage(JoyTctyVC())
+        }
+        else{
+            App.shared.goToPage(DcTctyVC())
+        }
+    }
+    static func gotoDialogTranslate(){
+        if CCAiManager.isJoy {
+            App.shared.goToPage(JoyDialogTranslateVC())
+        }
+        else{
+            App.shared.goToPage(DcDialogTranslateVC())
+        }
+    }
+}
index 953f1b9..a11f84d 100644 (file)
@@ -14,4 +14,23 @@ public class CCRole{
     public var type:String = ""
     /// 角色
     public var role:String = ""
+    
+    public init(){
+        
+    }
+    public init(sid: String, name: String, type: String, role: String) {
+        self.sid = sid
+        self.name = name
+        self.type = type
+        self.role = role
+    }
+    
+    static var xiaoYin:CCRole{
+        let model = CCRole()
+        model.sid = "zh_female_tianmeitaozi_mars_bigtts"
+        model.name = "小音"
+        model.type = "default"
+        model.role = "test_xiaoyin"
+        return model
+    }
 }
diff --git a/ShenYin/ShenYin/Sdk/CCSdkManager.swift b/ShenYin/ShenYin/Sdk/CCSdkManager.swift
deleted file mode 100644 (file)
index fe99743..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-//
-//  CCSdkManager.swift
-//  ShenYin
-//
-//  Created by liangshi on 2025/12/25.
-//
-
-class CCSdkManager {
-    
-    
-    
-}
diff --git a/ShenYin/ShenYin/Sdk/DcSdk/DCChatVC.swift b/ShenYin/ShenYin/Sdk/DcSdk/DCChatVC.swift
deleted file mode 100644 (file)
index 51d58a7..0000000
+++ /dev/null
@@ -1,218 +0,0 @@
-////
-////  DCChatVC.swift
-////  ShenYin
-////
-////  Created by liangshi on 2025/12/26.
-////
-//
-//import UIKit
-//import AVFAudio
-//import DeepChordsSdk
-//class DCChatVC:BaseVC{
-//    let rightItem = ChatNavigationBarItem(frame: CGRect(x: 0, y: 0, width: 80, height: 44))
-//    let roleItem = ChatRoleItem(frame: CGRect(x: 0, y: 0, width: 100, height: 44))
-//    
-//    let toolBar = ChatRecordBar()
-//    let tableView = UITableView(frame: CGRectZero, style: .plain)
-//    let headerView = ChatHeaderView(frame: CGRect(x: 0, y: 0, width: kScreenWid, height: 260))
-//    let tipsLabel = UILabel()
-//    
-//    var roleList:[DCRole] = []
-//    
-//    deinit {
-//        PcmSound.sharedInstance().stopAudioUnit()
-//        PcmSound.sharedInstance().cleanup()
-//    }
-//    
-//    override func viewDidLoad() {
-//        super.viewDidLoad()
-//    }
-//    override func setupViews() {
-//        
-//        navBar.rightView.isHidden = true
-//        navBar.addSubviews([roleItem,rightItem])
-//        roleItem.yc.addTap(target: self, action: #selector(roleBtnAction))
-//        rightItem.yc.addTap(target: self, action: #selector(rightBtnAction))
-//        
-//        tableView.backgroundColor = .clear
-//        tableView.delegate = self
-//        tableView.dataSource = self
-//        tableView.estimatedRowHeight = 60
-//        tableView.separatorStyle = .none
-//        tableView.register(ChatCell.self, forCellReuseIdentifier: ChatCell.reuseID)
-//        tableView.tableHeaderView = headerView
-//        
-//        view.addSubviews([tableView,toolBar,tipsLabel])
-//        toolBar.delegate = self
-//        
-//        tipsLabel.do { v in
-//            v.text = "内容由AI生成,请仔细甄别".l10n
-//            v.font = .regular(10)
-//            v.textColor = .hexString("#919191")
-//        }
-//    }
-//    override func setupLayout() {
-//        rightItem.snp.makeConstraints { (m) in
-//            m.top.equalTo(kNavTop)
-//            m.bottom.equalToSuperview()
-//            m.width.greaterThanOrEqualTo(80)
-//            m.right.equalToSuperview()
-//        }
-//        roleItem.snp.makeConstraints { (m) in
-//            m.top.equalTo(kNavTop)
-//            m.bottom.equalToSuperview()
-//            m.width.greaterThanOrEqualTo(80)
-//            m.left.equalToSuperview().inset(48)
-//        }
-//        toolBar.snp.makeConstraints { make in
-//            make.width.centerX.equalToSuperview()
-//            make.height.equalTo(52)
-//            make.bottom.equalToSuperview().inset(35)
-//        }
-//        tipsLabel.snp.makeConstraints { make in
-//            make.top.equalTo(toolBar.snp.bottom).offset(4)
-//            make.centerX.equalToSuperview()
-//        }
-//        tableView.snp.makeConstraints { make in
-//            make.top.equalToSuperview().inset(kNavBtm)
-//            make.left.right.equalToSuperview()
-//            make.bottom.equalTo(toolBar.snp.top)
-//        }
-//    }
-//    override func setupData() {
-//        DCChatManager.shared.chatNeedReloadData = {[weak self]  in
-//            guard let self = self else{return}
-//            tableView.reloadData()
-//            if DCChatManager.shared.dataList.count > 0 {
-//                tableView.scrollToRow(at: IndexPath(row: DCChatManager.shared.dataList.count - 1, section: 0), at: .none, animated: true)
-//            }
-//        }
-//        DCChatManager.shared.codeCallback = { [weak self] code in
-//            guard let self = self else{return}
-//            if code == 20001 {
-//                toolBar.recordBtn.state = .start
-//            }
-//            else if code == 20002 || code == 20003{
-//                toolBar.recordBtn.state = .finish
-//            }
-//            
-//        }
-//        DCChatManager.shared.finishCallback = { [weak self] path in
-//            guard let self = self else{return}
-//            //            self.recordBtn.isSelected = false
-//        }
-//
-//        
-//        refreshData()
-//        loadRoleList()
-//    }
-//    override func viewWillDisappear(_ animated: Bool) {
-//        super.viewWillDisappear(animated)
-//        
-//        ///结束对话,但并不会终止正在播放的录音
-//        DCChatManager.shared.stopChat()
-//        
-//    }
-//    override func viewDidAppear(_ animated: Bool) {
-//        super.viewDidAppear(animated)
-//        refreshData()
-//    }
-//    override func viewDidDisappear(_ animated: Bool) {
-//        super.viewDidDisappear(animated)
-//        ChatCache.shared.saveList(JoySdkManager.shared.chat.dataList)
-//    }
-//
-//    @objc func rightBtnAction() {
-//        App.shared.goToPage(IntelligentVC())
-//    }
-//    @objc func roleBtnAction(){
-//        if roleList.count == 0 {
-//            loadRoleList()
-//            return
-//        }
-//        let vc = RoleListVC(list: roleList, selectedModel: roleItem.role) {[weak self] model in
-//            guard let self = self else{return}
-//            roleItem.role = model
-//            headerView.update(name: model.name, role: model.role)
-//            JoyGemChatManager.shared().setSpeaker(model)
-//        }
-//        App.shared.goToPage(vc)
-//    }
-//    func refreshData(){
-//        tableView.reloadData()
-//        if JoySdkManager.shared.chat.dataList.count > 0 {
-//            tableView.scrollToRow(at: IndexPath(row: JoySdkManager.shared.chat.dataList.count - 1, section: 0), at: .none, animated: true)
-//        }
-//    }
-//    func loadRoleList(){
-//        DCChatManager.shared.getVcSpeekerList {[weak self] speakers in
-//            guard let self = self,speakers.count > 0 else{return}
-//            self.roleList = speakers
-//            self.roleList.insert(xiaoYinRole(), at: 0)
-//            
-//            var currentRole = roleList.first
-//            if let cacheRole = RoleCache.get(){
-//                for item in roleList{
-//                    if item.role == cacheRole {
-//                        currentRole = item
-//                        break
-//                    }
-//                }
-//            }
-//            self.roleItem.role = currentRole
-//            self.headerView.update(name: self.roleItem.role.name, role: self.roleItem.role.role)
-//            JoyGemChatManager.shared().setSpeaker(self.roleItem.role)
-//        }
-//    }
-//    func xiaoYinRole()->JoyGemSpeaker{
-//        let model = JoyGemSpeaker()
-//        model.sid = "zh_female_tianmeitaozi_mars_bigtts"
-//        model.name = "小音"
-//        model.type = "default"
-//        model.role = "test_xiaoyin"
-//        return model
-//    }
-//}
-//
-//extension DCChatVC:ChatRecordBarDelegate{
-//    func recordBarCancel(_ toolBar: ChatRecordBar) {
-//        //        JoyGemChatManager.shared().stopChat()
-//        JoyGemChatManager.shared().stopSingleChat()
-//        if let item = JoySdkManager.shared.chat.dataList.last,item.content.isEmpty{
-//            JoySdkManager.shared.chat.dataList.removeLast()
-//            refreshData()
-//        }
-//    }
-//    
-//    func recordBarStart(_ toolBar: ChatRecordBar) {
-//        //        let model = ChatCellModel()
-//        //        model.type = 1
-//        //        model.content = ""
-//        //        model.id = -1
-//        //        JoySdkManager.shared.dataList.append(model)
-//        //        refreshData()
-//        
-//        //        JoyGemChatManager.shared().stopChat()
-//        JoyGemChatManager.shared().startSingleChat()
-//        DCChatManager.shared.startChat()
-//    }
-//    
-//    func recordBarStop(_ toolBar: ChatRecordBar) {
-//        JoyGemChatManager.shared().stopSingleChat()
-//    }
-//}
-//
-//extension DCChatVC:UITableViewDelegate,UITableViewDataSource {
-//    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-//        return JoySdkManager.shared.chat.dataList.count
-//    }
-//    
-//    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-//        let cell = ChatCell.cell(withTableView: tableView, indexPath: indexPath)
-//        cell.data = JoySdkManager.shared.chat.dataList[indexPath.row]
-//        return cell
-//    }
-//    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-//        
-//    }
-//}
similarity index 85%
rename from ShenYin/ShenYin/Sdk/DcSdk/BleManager.swift
rename to ShenYin/ShenYin/Sdk/DcSdk/DcBleManager.swift
index 3952660..dd6d19d 100644 (file)
@@ -1,5 +1,5 @@
 ////
-////  BleManager.swift
+////  DcBleManager.swift
 ////  Example
 ////
 ////  Created by liangshi on 2025/11/24.
@@ -16,7 +16,7 @@
 //import CoreBluetooth
 //import DeepChordsSdk
 //
-//class BleManager: NSObject {
+//class DcBleManager: NSObject {
 //    var discoverDeviceCallback: (([CBPeripheral]) -> Void)?
 //    var didConnectDeviceCallback: ((CBPeripheral) -> Void)?
 //    var disConnectDeviceCallback: ((CBPeripheral) -> Void)?
@@ -30,7 +30,7 @@
 //        centralManager = CBCentralManager(delegate: self, queue: nil)
 //    }
 //    
-//    static let shared = BleManager()
+//    static let shared = DcBleManager()
 //
 //    func stopScanDevice() {
 //        centralManager.stopScan()
@@ -60,7 +60,7 @@
 ////        }
 //    }
 //}
-//extension BleManager:CBCentralManagerDelegate{
+//extension DcBleManager:CBCentralManagerDelegate{
 //    func centralManagerDidUpdateState(_ central: CBCentralManager) {
 //        DCLogger.debug("蓝牙状态",central.state.rawValue)
 //        switch central.state {
 //        
 //        peripheral.delegate = self
 //        peripheral.discoverServices(nil);
-//        DCBleManager.shared.centralManager(central, didConnect: peripheral)
+//        DCDcBleManager.shared.centralManager(central, didConnect: peripheral)
 //        self.didConnectDeviceCallback?(peripheral)
 //        App.shared.window?.makeToast("蓝牙已连接")
 //    }
 //    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
-//        DCBleManager.shared.centralManager(central, didFailToConnect: peripheral, error: error)
+//        DCDcBleManager.shared.centralManager(central, didFailToConnect: peripheral, error: error)
 //    }
 //    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
 //        disConnectDeviceCallback?(peripheral)
-//        DCBleManager.shared.centralManager(central, didDisconnectPeripheral: peripheral, error: nil)
+//        DCDcBleManager.shared.centralManager(central, didDisconnectPeripheral: peripheral, error: nil)
 //        App.shared.window?.makeToast("蓝牙已断开连接")
 //        App.shared.window?.rootViewController = UINavigationController(rootViewController: DeviceListVC())
 //    }
 //}
 //
-//extension BleManager: CBPeripheralDelegate {
+//extension DcBleManager: CBPeripheralDelegate {
 //    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
-//        DCBleManager.shared.peripheral(peripheral, didDiscoverServices: error)
+//        DCDcBleManager.shared.peripheral(peripheral, didDiscoverServices: error)
 //    }
 //    
 //    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
-//        DCBleManager.shared.peripheral(peripheral, didDiscoverCharacteristicsFor: service, error: error)
+//        DCDcBleManager.shared.peripheral(peripheral, didDiscoverCharacteristicsFor: service, error: error)
 //    }
 //    
 //    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: (any Error)?) {
 //    }
 //    
 //    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
-//        DCBleManager.shared.peripheral(peripheral, didUpdateValueFor: characteristic, error: error)
+//        DCDcBleManager.shared.peripheral(peripheral, didUpdateValueFor: characteristic, error: error)
 //
 //    }
 //    
@@ -1,5 +1,5 @@
 //
-//  DCChatManager.swift
+//  DcChatManager.swift
 //  Example
 //
 //  Created by liangshi on 2025/12/15.
@@ -20,8 +20,8 @@ import DeepChordsSdk
 //};
 
 
-class DCChatManager:NSObject{
-    static let shared = DCChatManager()
+class DcChatManager:NSObject{
+    static let shared = DcChatManager()
     private var isTtsPlaying = false
     private var audioData: Data?
     
@@ -44,25 +44,20 @@ class DCChatManager:NSObject{
     public func startChat(){
         DCChat.shared.startChat()
     }
-    public func getVcSpeekerList(finished:@escaping ([DCRole]?,Bool)->Void){
-        DCChat.shared.getVcSpeekerList(finished: finished)
-    }
-    
-    func preparePlay(){
-        PcmSound.sharedInstance().cleanup()
-        PcmSound.sharedInstance().setup(16000, withchannels: 1)
-        
-        var options: AVAudioSession.CategoryOptions = [.allowBluetooth]
-        do {
-            try AVAudioSession.sharedInstance().setCategory(.playback, options: [options])
-            try AVAudioSession.sharedInstance().setActive(true)
-        } catch {
-            DCLogger.debug(error)
+    public func getVcSpeekerList(finished:@escaping ([DCRole]?)->Void){
+        DCChat.shared.getVcSpeekerList { list, success in
+            if success {
+                finished(list)
+            }
+            else{
+                finished(nil)
+            }
         }
     }
+
     
     func setupChat(){
-        preparePlay()
+        CCAiManager.preparePlayBlueTooth()
         DCChat.shared.role = role
         DCChat.shared.asrCallback = {[weak self] (text,index) in
             guard let self = self else{return}
@@ -124,7 +119,7 @@ class DCChatManager:NSObject{
             }
         }
         DCChat.shared.finishCallback = {[weak self] path in
-            self?.preparePlay()
+            CCAiManager.preparePlayBlueTooth()
             self?.finishCallback?(path)
         }
         
@@ -155,7 +150,7 @@ class DCChatManager:NSObject{
 //        }
 //    }
 }
-extension DCChatManager : PcmSoundDelegate {
+extension DcChatManager : PcmSoundDelegate {
     func remaining(_ frames: UInt) {
         DCLogger.debug("[frame:\(frames)]");
         if (frames == 0) {
diff --git a/ShenYin/ShenYin/Sdk/DcSdk/DcChinese2Enligsh.swift b/ShenYin/ShenYin/Sdk/DcSdk/DcChinese2Enligsh.swift
new file mode 100644 (file)
index 0000000..157223b
--- /dev/null
@@ -0,0 +1,92 @@
+//
+//  DcChinese2Enligsh.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/12/29.
+//
+
+import DeepChordsSdk
+
+class DcChinese2Enligsh:NSObject{
+    var dataList:[(Chinese2EnglishCellModel,Int)] = []
+    var startIndex = 0//记录开始录音的次数,用于区分返回的数据
+    
+    var stopCallback:(()->Void)?
+    var refreshDataCallback:(()->Void)?
+    override init(){
+        super.init()
+
+    }
+    
+    func start(){
+        setupSdk()
+        startIndex += 100000
+        
+        DCChinse2En.shared.fromLanguage = "auto_lang"
+        DCChinse2En.shared.toLanguage = "auto_lang"
+        DCChinse2En.shared.isTranslate = true
+        DCChinse2En.shared.isTTs = true
+        DCChinse2En.shared.start()
+    }
+    
+    func stop(){
+        DCChinse2En.shared.stop()
+    }
+    
+    func setupSdk(){
+        DCChinse2En.shared.asrCallback = { [weak self] (text,index) in
+            guard let self = self else{return}
+            Logger.debug("asr: \(text),index: \(index)")
+            let msg = dataList.firstIndex (where: { (str,idx) in
+                return idx == Int(index) + self.startIndex
+            })
+            if (msg == nil) {
+                dataList.append((Chinese2EnglishCellModel(send: text),Int(index) + startIndex))
+            } else {
+                let (item,_) = dataList[msg!]
+                item.send =  text
+            }
+            //
+            //        ///监听到一句话结束,自动结束当前录音
+            //        if (model.definite == true) {
+            //            stopEarRecord()
+            //        }
+            refreshDataCallback?()
+        }
+        DCChinse2En.shared.translateCallback = { [weak self]  (text,index) in
+            guard let self = self else{return}
+            Logger.debug("translate: \(text),index: \(index)")
+            let msg = dataList.firstIndex (where: { (str,idx) in
+                return idx == Int(index) + self.startIndex
+            })
+            if (msg == nil) {
+                dataList.append((Chinese2EnglishCellModel(response: text),Int(index) + startIndex))
+            } else {
+                let (item,_) = dataList[msg!]
+                item.response =  text
+            }
+            refreshDataCallback?()
+        }
+        DCChinse2En.shared.translateDataCallback = { data in
+            PcmSound.sharedInstance().feed(data)
+        }
+        DCChinse2En.shared.codeCallback = {  [weak self] code in
+            guard let self = self else{return}
+            if code == 20001 {
+            }
+            else if code == 20002 || code == 20003{
+                stopCallback?()
+            }
+        }
+        DCChinse2En.shared.filePathCallBack = { [weak self] path in
+            guard let self = self else{return}
+        }
+    }
+    
+}
+
+extension DcChinese2Enligsh:PcmSoundDelegate{
+    func remaining(_ frames: UInt) {
+        
+    }
+}
similarity index 56%
rename from ShenYin/ShenYin/Sdk/DcSdk/DCSdkManager.swift
rename to ShenYin/ShenYin/Sdk/DcSdk/DcSdkManager.swift
index 0ca4e8a..a0b4ff9 100644 (file)
@@ -1,13 +1,13 @@
 //
-//  DCSdkManager.swift
+//  DcSdkManager.swift
 //  Example
 //
 //  Created by liangshi on 2025/12/19.
 //
 import DeepChordsSdk
 
-class DCSdkManager{
-    static let shared = DCSdkManager()
+class DcSdkManager{
+    static let shared = DcSdkManager()
     public var authCallback:((_ success:Bool)->Void)?
     
     
@@ -17,57 +17,80 @@ class DCSdkManager{
             DCLogger.debug(code,msg)
             guard let self = self else{return}
             if code == 200{
-                joyGemAuthSuccessCallback()
+                authSuccessCallback()
             }
             else{
-                joyGemAuthFailedCallback()
+                authFailedCallback()
             }
         }
-//        DCRecordManager.shared.setupRecord()
-        DCChatManager.shared.setupChat()
+        DcChatManager.shared.setupChat()
         
+        /// ai事件回调
+        /// 0x01(打开AI对话)
+        //    0x02(关闭AI对话)
+        //    0x03(打开耳机录音)
+        //    0x04(关闭耳机录音)
         DCDevice.shared.eventCallback = { event in
             switch event {
             case 0x01:
-                if let vc = App.shared.topVC() as? ChatVC {
+//                0x01(打开AI对话)
+                if let vc = App.shared.topVC() as? DcChatVC {
                     //                    vc.startRecord()
                 }else{
-                    App.shared.goToPage(ChatVC())
+                    App.shared.goToPage(DcChatVC())
                 }
                 break
             case 0x02:
+//                0x02(关闭AI对话)
 //                if let vc = App.shared.topVC() as? ChatVC {
 //                    //                    vc.startRecord()
 //                }else{
 //                    App.shared.goToPage(ChatVC())
 //                }
+                DcChatManager.shared.stopChat()
                 break
             case 0x03:
-//                if let vc = App.shared.topVC() as? EarPhoneRecordVC {
-//                    //                    vc.startRecord()
-//                }else{
-//                    App.shared.goToPage(EarPhoneRecordVC())
-//                }
+                //0x03(打开耳机录音)
+                if CCCall.shared.isInCall {
+                    if let vc = App.shared.topVC() as? DcStereoCallVC {
+                        vc.startRecord()
+                    }
+                    else{
+                        App.shared.goToPage(DcStereoCallVC(autoStart: true))
+                        DcXcly.shared.recordStart()
+                    }
+                }
+                else{
+                    if let vc = App.shared.topVC() as? DcXclyVC {
+                        vc.startRecord()
+                    }
+                    else{
+                        App.shared.goToPage(DcXclyVC(autoStart: true))
+                        DcXcly.shared.recordStart()
+                    }
+                }
                 break
             case 0x04:
-//                if let vc = App.shared.topVC() as? EarPhoneRecordVC {
-//                    //                    vc.startRecord()
-//                }else{
-//                    App.shared.goToPage(EarPhoneRecordVC())
-//                }
+                //    0x04(关闭耳机录音)
+                if let topVC = App.shared.topVC(), let vc = topVC as? DcXclyVC {
+                    vc.stopRecord()
+                }
+                if let topVC = App.shared.topVC(), let vc = topVC as? DcCallingRecordVC {
+                    vc.stopRecord()
+                }
                 break
             default:
                 break
             }
         }
     }
-    func joyGemAuthFailedCallback() {
+    func authFailedCallback() {
         Logger.debug("鉴权失败")
         YLprogressHUD.showAutoHiden("鉴权失败")
     }
-    func joyGemAuthSuccessCallback() {
+    func authSuccessCallback() {
         Logger.debug("鉴权成功")
-        App.shared.preparePlay()
+        CCAiManager.preparePlayBlueTooth()
         
         DeviceCache.saveLastConnected(mac: CCBleManager.shared.connectPeripheral!.mac)
         DeviceCache.shared.add(CCBleManager.shared.connectPeripheral!.device.identifier.uuidString, mac: CCBleManager.shared.connectPeripheral!.mac)
@@ -77,7 +100,7 @@ class DCSdkManager{
         DeviceVM.updateOtaVersion()
     }
 }
-extension DCSdkManager{
+extension DcSdkManager{
     static func getMac(_ dict:[String:Any])->String?{
         
         if let advMsd = dict[CBAdvertisementDataManufacturerDataKey] as? Data {
diff --git a/ShenYin/ShenYin/Sdk/DcSdk/DcTcty.swift b/ShenYin/ShenYin/Sdk/DcSdk/DcTcty.swift
new file mode 100644 (file)
index 0000000..cbf25be
--- /dev/null
@@ -0,0 +1,93 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/12/29.
+//
+
+import DeepChordsSdk
+
+class DcTcty:NSObject{
+    var messageList : [(String,UInt)] = []
+    var translateList : [(String,UInt)] = []
+    var recordFilePath : String?
+    
+    
+    var stopCallback:(()->Void)?
+    var refreshOriginCallback:((String)->Void)?
+    var refreshTranslateCallback:((String)->Void)?
+    var filePathCallback:((String)->Void)?
+    
+    override init(){
+        super.init()
+        
+        
+    }
+
+    func setupSdk(){
+        DCPhoneRecord.shared.asrCallback = { [weak self] text in
+            guard let self = self else{return}
+            refreshOriginCallback?(text)
+        }
+        DCPhoneRecord.shared.translateCallback = { [weak self] text in
+            guard let self = self else{return}
+            refreshTranslateCallback?(text)
+        }
+        DCPhoneRecord.shared.translateDataCallback = { data in
+            PcmSound.sharedInstance().feed(data)
+        }
+        DCPhoneRecord.shared.codeCallback = {  [weak self] code in
+            guard let self = self else{return}
+            if code == 20001 {
+            }
+            else if code == 20002 || code == 20003{
+                stopCallback?()
+            }
+        }
+        DCPhoneRecord.shared.filePathCallBack = { [weak self] path in
+            guard let self = self else{return}
+            recordFilePath = path
+            filePathCallback?(path ?? "")
+        }
+    }
+    func start(from:String,to:String){
+        setupSdk()
+        messageList.removeAll()
+        translateList.removeAll()
+        
+        if Thread.isMainThread {
+            do {
+                try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [])
+                try AVAudioSession.sharedInstance().setActive(true)
+            } catch let error as NSError {
+                Logger.debug("An error occurred setting the audio session category: \(error.localizedDescription)")
+            }
+        } else {
+            DispatchQueue.main.sync {
+                do {
+                    try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [])
+                    try AVAudioSession.sharedInstance().setActive(true)
+                } catch let error as NSError {
+                    Logger.debug("An error occurred setting the audio session category: \(error.localizedDescription)")
+                }
+            }
+        }
+        
+        DCPhoneRecord.shared.fromLanguage = from
+        DCPhoneRecord.shared.toLanguage = to
+        DCPhoneRecord.shared.isTranslate = true
+        DCPhoneRecord.shared.isTTs = true
+        DCPhoneRecord.shared.start()
+    }
+    
+    func stop(){
+        DCPhoneRecord.shared.stop()
+    }
+    
+}
+
+extension DcTcty : PcmSoundDelegate {
+    func remaining(_ frames: UInt) {
+        Logger.debug("[frame:\(frames)]");
+    }
+}
diff --git a/ShenYin/ShenYin/Sdk/DcSdk/DcThly.swift b/ShenYin/ShenYin/Sdk/DcSdk/DcThly.swift
new file mode 100644 (file)
index 0000000..47106fc
--- /dev/null
@@ -0,0 +1,79 @@
+//
+//  DcThly.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/12/29.
+//
+
+import UIKit
+import DeepChordsSdk
+
+class DcThly:NSObject{
+    static let shared = DcThly()
+    
+    var recordFinish:((String)->Void)?
+    var recordNeedReloadData:((String)->Void)?
+    var recordList : [(String,UInt)] = []
+    var recordText = ""
+    /// 状态码回调
+    public var codeCallback:((Int)->Void)?
+    public var callRecordNotOnCall:(()->Void)?
+    
+    
+    override init() {
+        super.init()
+        setupSdk()
+    }
+    func setupSdk(){
+        DCBleRecord.shared.asrCallback = { [weak self] text in
+            guard let self = self else{return}
+            recordText = text
+            recordNeedReloadData?(text)
+        }
+        //        DCBleRecord.shared.translateCallback = { [weak self] text in
+        //            guard let self = self else{return}
+        //            self.toTextView.text = text
+        //            let range = NSMakeRange(toTextView.text.count - 1, 1)
+        //            self.toTextView.scrollRangeToVisible(range)
+        //        }
+        DCBleRecord.shared.codeCallback = { [weak self] code in
+            guard let self = self else{return}
+            codeCallback?(code)
+        }
+        DCBleRecord.shared.filePathCallBack = { [weak self] path in
+            guard let self = self else{return}
+            //            self.recordBtn.isSelected = false
+            if recordText.isEmpty{
+                return
+            }
+            guard let path = path,path.isNotEmpty else{
+                return
+            }
+            guard let wavPath = DCFolder.recordFileResave(srcPath: path) else{return}
+            recordFinish?(wavPath)
+        }
+    }
+    func setRecordLanage(_ language:CCAiLanguage){
+        DCBleRecord.shared.fromLanguage = language.value
+    }
+    func recordStart(){
+        recordList.removeAll()
+        recordText = ""
+        setupSdk()
+        if !CCCall.shared.isInCall {
+            callRecordNotOnCall?()
+            return
+        }
+        Logger.debug("开启通话录音")
+        DCBleRecord.shared.start(type: 0x00 )
+    }
+    func recordResume(){
+        //        JoyGemRecordManager.shared().resume()
+    }
+    func recordPause(){
+        //        JoyGemRecordManager.shared().pause()
+    }
+    func recordStop(){
+        DCBleRecord.shared.stop()
+    }
+}
diff --git a/ShenYin/ShenYin/Sdk/DcSdk/DcXcly.swift b/ShenYin/ShenYin/Sdk/DcSdk/DcXcly.swift
new file mode 100644 (file)
index 0000000..22e90e9
--- /dev/null
@@ -0,0 +1,67 @@
+//
+//  DcXcly.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/12/26.
+//
+
+import UIKit
+import DeepChordsSdk
+
+class DcXcly:NSObject{
+    static let shared = DcXcly()
+    
+    var recordFinish:((String)->Void)?
+    var recordNeedReloadData:((String)->Void)?
+    var recordList : [(String,UInt)] = []
+    var recordText = ""
+    /// 状态码回调
+    public var codeCallback:((Int)->Void)?
+    
+    override init() {
+        super.init()
+        setupSdk()
+    }
+    func setupSdk(){
+        DCBleRecord.shared.asrCallback = { [weak self] text in
+            guard let self = self else{return}
+            recordText = text
+            recordNeedReloadData?(text)
+        }
+        DCBleRecord.shared.codeCallback = { [weak self] code in
+            guard let self = self else{return}
+            codeCallback?(code)
+        }
+        DCBleRecord.shared.filePathCallBack = { [weak self] path in
+            guard let self = self else{return}
+            //            self.recordBtn.isSelected = false
+            if recordText.isEmpty{
+                return
+            }
+            guard let path = path,path.isNotEmpty else{
+                return
+            }
+            guard let wavPath = DCFolder.recordFileResave(srcPath: path) else{return}
+            recordFinish?(wavPath)
+        }
+    }
+    func setRecoordLanage(_ language:CCAiLanguage){
+        DCBleRecord.shared.fromLanguage = language.value
+    }
+    func recordStart(){
+        Logger.debug("开启录音")
+        recordList.removeAll()
+        recordText = ""
+        setupSdk()
+        DCBleRecord.shared.start(type: 0x0c )
+    }
+    func recordResume(){
+//        JoyGemRecordManager.shared().resume()
+    }
+    func recordPause(){
+//        JoyGemRecordManager.shared().pause()
+    }
+    func recordStop(){
+        DCBleRecord.shared.stop()
+    }
+}
diff --git a/ShenYin/ShenYin/Sdk/DcSdk/VC/DcCallingRecordVC.swift b/ShenYin/ShenYin/Sdk/DcSdk/VC/DcCallingRecordVC.swift
new file mode 100644 (file)
index 0000000..536e48f
--- /dev/null
@@ -0,0 +1,177 @@
+
+//
+//  DcCallingRecordVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/8/6.
+//
+
+import UIKit
+import AVFAudio
+class DcCallingRecordVC:BaseVC{
+    let toolBar = FieldRecordBar()
+    let textView = UITextView()
+    let rhythView = RhythView()
+    
+    var autoStart = false
+    var needSave = true
+    var bgEnter = false//是否是APP在后台进入这个页面的
+    
+    init(autoStart :Bool = false) {
+        self.autoStart = autoStart
+        super.init()
+        bgEnter = App.shared.isInBackground
+        setupSdk()
+    }
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+    }
+    override func setupViews() {
+        navBar.title = "通话录音".l10n
+        navBar.backgroundColor = .white
+        
+        toolBar.do { v in
+            v.delegate = self
+            v.hasRightItem = false
+            v.leftItem.language = AiLanguageCache.get(type: .thly) ?? .chinse
+        }
+        
+        view.backgroundColor = .white
+        
+        textView.do { v in
+            v.backgroundColor = .white
+            v.isEditable = false
+            v.font = .regular(14)
+            v.text = ""
+            v.textColor = .hexString("#121212")
+            v.contentInset = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 18)
+        }
+        view.addSubviews([rhythView,textView,toolBar])
+        
+    }
+    override func setupLayout() {
+        rhythView.snp.makeConstraints { make in
+            make.width.centerX.equalToSuperview()
+            make.top.equalToSuperview().inset(kNavBtm)
+            make.height.equalTo(110)
+        }
+        toolBar.snp.makeConstraints { make in
+            make.width.centerX.equalToSuperview()
+            make.height.equalTo(120)
+            make.bottom.equalToSuperview().inset(20)
+        }
+        textView.snp.makeConstraints { make in
+            make.top.equalTo(rhythView.snp.bottom)
+            make.left.right.equalToSuperview()
+            make.bottom.equalTo(toolBar.snp.top)
+        }
+    }
+    override func setupData() {
+        if bgEnter {
+            textView.text = DcThly.shared.recordText
+        }
+    }
+    func setupSdk(){
+        DcThly.shared.recordFinish = {[weak self] path in
+            guard let self = self else { return  }
+            if !needSave {
+                return
+            }
+            let v = PopTextField()
+            v.show()
+            v.cancelTap = {
+                App.shared.popPage()
+            }
+            v.okTap = { [weak self]  text in
+                guard let self = self else{return}
+                
+                let model = RecordDetailModel()
+                model.type = .phone
+                model.from = toolBar.leftItem.language
+                model.to = .english
+                model.name = text
+                model.path = path
+                model.duration = YLToos.getDuration(model.path)
+                model.origin = textView.text
+                model.translate = ""
+                App.shared.popPage(animated: false)
+                App.shared.goToPage(RecordDetailVC(record: model))
+            }
+        }
+        DcThly.shared.recordNeedReloadData = {[weak self] text in
+            guard let self = self else { return  }
+            textView.text = text
+            
+            let range = NSMakeRange(textView.text.count - 1, 1)
+            textView.scrollRangeToVisible(range)
+        }
+        //需要完善
+//        DcThly.shared.callRecordAutoFinish = {[weak self]  in
+//            guard let self = self else { return  }
+//            stopAction()
+//        }
+        DcThly.shared.callRecordNotOnCall = {[weak self]  in
+            guard let self = self else { return  }
+            YLprogressHUD.showAutoHiden("未处于通话状态".l10n)
+            rhythView.reset()
+            toolBar.recordBtn.isSelected = false
+            toolBar.leftItem.alpha = 1
+        }
+        
+        if autoStart{
+            startRecord()
+        }
+    }
+    
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = false
+    }
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = true
+    }
+    override func navleftViewTapped() {
+        if toolBar.recordBtn.isSelected {
+            CustomAlert.showWith(title: "提示".l10n, messgage: "当前正在录音,确定放弃当前录音吗?".l10n, cancelCallback: {}) {[unowned self] in
+                self.rhythView.stopTimer()
+                self.needSave = false
+                DcThly.shared.recordStop()
+                self.navigationController?.popViewController(animated: true)
+            }
+        }
+        else{
+            super.navleftViewTapped()
+        }
+    }
+    func startRecord(){
+        toolBar.recordBtnAction()
+    }
+    func stopRecord(){
+        toolBar.recordBtnAction()
+    }
+}
+
+extension DcCallingRecordVC:FieldRecordBarDelegate{
+    func fieldRecordBar(_ bar: FieldRecordBar, languageChange language: CCAiLanguage) {
+        DcThly.shared.setRecordLanage(language)
+        AiLanguageCache.save(type: .thly, lan: language)
+    }
+    
+    func fieldRecordBar(_ bar: FieldRecordBar, stateChange state: RecordSate) {
+        if state == .start {
+            textView.text = ""
+            rhythView.startTimer()
+            DcThly.shared.recordStart()
+        }
+        else if state == .stop{
+            stopAction()
+        }
+    }
+    func stopAction(){
+        rhythView.stopTimer()
+        DcThly.shared.recordStop()
+        toolBar.startAnimating()
+    }
+}
diff --git a/ShenYin/ShenYin/Sdk/DcSdk/VC/DcChatVC.swift b/ShenYin/ShenYin/Sdk/DcSdk/VC/DcChatVC.swift
new file mode 100644 (file)
index 0000000..5b4b3f2
--- /dev/null
@@ -0,0 +1,201 @@
+////
+////  DcChatVC.swift
+////  ShenYin
+////
+////  Created by liangshi on 2025/12/26.
+////
+//
+import UIKit
+import AVFAudio
+import DeepChordsSdk
+class DcChatVC:BaseVC{
+    let rightItem = ChatNavigationBarItem(frame: CGRect(x: 0, y: 0, width: 80, height: 44))
+    let roleItem = ChatRoleItem(frame: CGRect(x: 0, y: 0, width: 100, height: 44))
+    
+    let toolBar = ChatRecordBar()
+    let tableView = UITableView(frame: CGRectZero, style: .plain)
+    let headerView = ChatHeaderView(frame: CGRect(x: 0, y: 0, width: kScreenWid, height: 260))
+    let tipsLabel = UILabel()
+    
+    var roleList:[CCRole] = []
+    
+    deinit {
+        CCAiManager.stopPlay()
+    }
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+    }
+    override func setupViews() {
+        
+        navBar.rightView.isHidden = true
+        navBar.addSubviews([roleItem,rightItem])
+        roleItem.yc.addTap(target: self, action: #selector(roleBtnAction))
+        rightItem.yc.addTap(target: self, action: #selector(rightBtnAction))
+        
+        tableView.backgroundColor = .clear
+        tableView.delegate = self
+        tableView.dataSource = self
+        tableView.estimatedRowHeight = 60
+        tableView.separatorStyle = .none
+        tableView.register(ChatCell.self, forCellReuseIdentifier: ChatCell.reuseID)
+        tableView.tableHeaderView = headerView
+        
+        view.addSubviews([tableView,toolBar,tipsLabel])
+        toolBar.delegate = self
+        
+        tipsLabel.do { v in
+            v.text = "内容由AI生成,请仔细甄别".l10n
+            v.font = .regular(10)
+            v.textColor = .hexString("#919191")
+        }
+    }
+    override func setupLayout() {
+        rightItem.snp.makeConstraints { (m) in
+            m.top.equalTo(kNavTop)
+            m.bottom.equalToSuperview()
+            m.width.greaterThanOrEqualTo(80)
+            m.right.equalToSuperview()
+        }
+        roleItem.snp.makeConstraints { (m) in
+            m.top.equalTo(kNavTop)
+            m.bottom.equalToSuperview()
+            m.width.greaterThanOrEqualTo(80)
+            m.left.equalToSuperview().inset(48)
+        }
+        toolBar.snp.makeConstraints { make in
+            make.width.centerX.equalToSuperview()
+            make.height.equalTo(52)
+            make.bottom.equalToSuperview().inset(35)
+        }
+        tipsLabel.snp.makeConstraints { make in
+            make.top.equalTo(toolBar.snp.bottom).offset(4)
+            make.centerX.equalToSuperview()
+        }
+        tableView.snp.makeConstraints { make in
+            make.top.equalToSuperview().inset(kNavBtm)
+            make.left.right.equalToSuperview()
+            make.bottom.equalTo(toolBar.snp.top)
+        }
+    }
+    override func setupData() {
+        DcChatManager.shared.chatNeedReloadData = {[weak self]  in
+            guard let self = self else{return}
+            tableView.reloadData()
+            if DcChatManager.shared.dataList.count > 0 {
+                tableView.scrollToRow(at: IndexPath(row: DcChatManager.shared.dataList.count - 1, section: 0), at: .none, animated: true)
+            }
+        }
+        DcChatManager.shared.codeCallback = { [weak self] code in
+            guard let self = self else{return}
+            if code == 20001 {
+                toolBar.recordBtn.state = .start
+            }
+            else if code == 20002 || code == 20003{
+                toolBar.recordBtn.state = .finish
+            }
+            
+        }
+        DcChatManager.shared.finishCallback = { [weak self] path in
+            guard let self = self else{return}
+            toolBar.recordBtn.state = .finish
+        }
+
+        
+        refreshData()
+        loadRoleList()
+    }
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        DcChatManager.shared.stopChat()
+    }
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        refreshData()
+    }
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        ChatCache.shared.saveList(DcChatManager.shared.dataList)
+    }
+
+    @objc func rightBtnAction() {
+        App.shared.goToPage(IntelligentVC())
+    }
+    @objc func roleBtnAction(){
+        if roleList.count == 0 {
+            loadRoleList()
+            return
+        }
+        let vc = RoleListVC(list: roleList, selectedModel: roleItem.role) {[weak self] model in
+            guard let self = self else{return}
+            roleItem.role = model
+            headerView.update(name: model.name, role: model.role)
+            DcChatManager.shared.role = DCRole(sid: model.sid, name: model.name, type: model.type, role: model.role)
+        }
+        App.shared.goToPage(vc)
+    }
+    func refreshData(){
+        tableView.reloadData()
+        if DcChatManager.shared.dataList.count > 0 {
+            tableView.scrollToRow(at: IndexPath(row: DcChatManager.shared.dataList.count - 1, section: 0), at: .none, animated: true)
+        }
+    }
+    func loadRoleList(){
+        DcChatManager.shared.getVcSpeekerList {[weak self] speakers in
+            guard let self = self,let speakers = speakers,speakers.count > 0 else{return}
+            self.roleList = speakers.map{CCRole(sid: $0.sid, name: $0.name, type: $0.type, role: $0.role)}
+            self.roleList.insert(CCRole.xiaoYin, at: 0)
+            
+            var currentRole = roleList.first
+            if let cacheRole = RoleCache.get(){
+                for item in roleList{
+                    if item.role == cacheRole {
+                        currentRole = item
+                        break
+                    }
+                }
+            }
+            guard let currentRole = currentRole else{return}
+            self.roleItem.role = currentRole
+            self.headerView.update(name: self.roleItem.role.name, role: self.roleItem.role.role)
+            DcChatManager.shared.role = DCRole(sid: currentRole.sid, name: currentRole.name, type: currentRole.type, role: currentRole.role)
+        }
+    }
+
+}
+
+extension DcChatVC:ChatRecordBarDelegate{
+    func recordBarCancel(_ toolBar: ChatRecordBar) {
+//        JoyGemChatManager.shared().stopSingleChat()
+        DcChatManager.shared.stopChat()
+        if let item = DcChatManager.shared.dataList.last,item.content.isEmpty{
+            DcChatManager.shared.dataList.removeLast()
+            refreshData()
+        }
+    }
+    
+    func recordBarStart(_ toolBar: ChatRecordBar) {
+//        JoyGemChatManager.shared().startSingleChat()
+        DcChatManager.shared.startChat()
+    }
+    
+    func recordBarStop(_ toolBar: ChatRecordBar) {
+//        JoyGemChatManager.shared().stopSingleChat()
+        DcChatManager.shared.stopChat()
+    }
+}
+
+extension DcChatVC:UITableViewDelegate,UITableViewDataSource {
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return DcChatManager.shared.dataList.count
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = ChatCell.cell(withTableView: tableView, indexPath: indexPath)
+        cell.data = DcChatManager.shared.dataList[indexPath.row]
+        return cell
+    }
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        
+    }
+}
diff --git a/ShenYin/ShenYin/Sdk/DcSdk/VC/DcChinese2EnglishVC.swift b/ShenYin/ShenYin/Sdk/DcSdk/VC/DcChinese2EnglishVC.swift
new file mode 100644 (file)
index 0000000..0406d2f
--- /dev/null
@@ -0,0 +1,142 @@
+//
+//  Chinese2EnglishVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/8/6.
+//
+
+import UIKit
+import AVFAudio
+class DcChinese2EnglishVC:BaseVC{
+    let toolBar = RecordBar()
+    let tableView = UITableView(frame: CGRectZero, style: .plain)
+    let bg = UIImageView()
+    let sdk = DcChinese2Enligsh()
+    var dataList:[(Chinese2EnglishCellModel,Int)] = []
+    
+    //    deinit {
+    //        PcmSound.sharedInstance().stopAudioUnit()
+    //        PcmSound.sharedInstance().cleanup()
+    //    }
+    
+    override func setupViews() {
+        navBar.title = "中英互译".l10n
+        navBar.rightView.isHidden = false
+        navBar.rightView.setImage(UIImage(named: "help_icon"), for: .normal)
+        view.addSubview(tableView)
+        view.addSubview(toolBar)
+        view.addSubview(bg)
+        
+        toolBar.delegate = self
+        toolBar.isChienseEnglish = true
+        toolBar.leftItem.valueLabel.text = "左耳".l10n
+        toolBar.rightItem.valueLabel.text = "右耳".l10n
+        
+        tableView.backgroundColor = .clear
+        tableView.delegate = self
+        tableView.dataSource = self
+        tableView.estimatedRowHeight = 60
+        tableView.separatorStyle = .none
+        tableView.register(Chinese2EnglishCell.self, forCellReuseIdentifier: Chinese2EnglishCell.reuseID)
+        
+        bg.image = UIImage(named: "translate_bg")
+    }
+    override func setupLayout() {
+        toolBar.snp.makeConstraints { make in
+            make.width.centerX.equalToSuperview()
+            make.height.equalTo(120)
+            make.bottom.equalToSuperview().inset(20)
+        }
+        tableView.snp.makeConstraints { make in
+            make.top.equalToSuperview().inset(80)
+            make.left.right.equalToSuperview()
+            make.bottom.equalTo(toolBar.snp.top)
+        }
+        bg.snp.makeConstraints { make in
+            make.size.equalTo(90)
+            make.center.equalTo(tableView)
+        }
+    }
+    override func setupData() {
+        if TipsCache.needShow(type: .chinese2english) {
+            RecordTipsView(type: .chinese2english).show()
+        }
+        sdk.stopCallback = {[weak self] in
+            guard let self = self else{return}
+            toolBar.recordBtn.isSelected = false
+        }
+        sdk.refreshDataCallback = {[weak self] in
+            guard let self = self else{return}
+            dataList = sdk.dataList
+            refreshData()
+        }
+    }
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = false
+        
+        PcmSound.sharedInstance().delegate = sdk
+        CCAiManager.preparePlayBlueTooth()
+    }
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = true
+        CCAiManager.preparePlayBlueTooth()
+    }
+    override func navleftViewTapped() {
+        if toolBar.recordBtn.isSelected {
+            self.sdk.stop()
+            //            CustomAlert.showWith(title: "提示".l10n, messgage: "当前正在录音,确定放弃当前录音吗".l10n, cancelCallback: {}) {
+            //                self.sdk.stop()
+            ////                PcmSound.sharedInstance().stopAudioUnit()
+            ////                PcmSound.sharedInstance().cleanup()
+            //                super.navleftViewTapped()
+            //            }
+        }
+        else{
+            super.navleftViewTapped()
+        }
+    }
+    override func navRightViewTapped() {
+        let tip = RecordTipsView(type: .chinese2english)
+        tip.show()
+    }
+    func refreshData(){
+        tableView.reloadData()
+        if dataList.count > 0 {
+            tableView.scrollToRow(at: IndexPath(row: dataList.count - 1, section: 0), at: .none, animated: true)
+            bg.isHidden = true
+        }
+        else{
+            bg.isHidden = false
+        }
+    }
+}
+extension DcChinese2EnglishVC:RecordBarDelegate{
+    func recordBarLanguageChange(_ bar: RecordBar) {
+        
+    }
+    func recordBar(_ bar: RecordBar, stateChange state: RecordBarSate) {
+        if state == .start {
+            sdk.start()
+        }
+        else if state == .stop{
+            sdk.stop()
+        }
+    }
+}
+extension DcChinese2EnglishVC:UITableViewDelegate,UITableViewDataSource {
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return dataList.count
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = Chinese2EnglishCell.cell(withTableView: tableView, indexPath: indexPath)
+        let (vallue,_) = dataList[indexPath.row]
+        cell.data = vallue
+        return cell
+    }
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        
+    }
+}
@@ -1,14 +1,14 @@
 //
-//  DialogTranslateVC.swift
+//  Untitled.swift
 //  ShenYin
 //
-//  Created by liangshi on 2025/8/6.
+//  Created by liangshi on 2025/12/29.
 //
 
 import UIKit
 import JoyGemAI
 import AVFoundation
-class DialogTranslateVC:BaseVC{
+class DcDialogTranslateVC:BaseVC{
     let toolBar = DialogRecordBar()
     var recordFilePath : String?
     
@@ -20,8 +20,7 @@ class DialogTranslateVC:BaseVC{
     var tmpOtherList : [(String,UInt)] = []
     var otherPcm = Data()
     deinit {
-        PcmSound.sharedInstance().stopAudioUnit()
-        PcmSound.sharedInstance().cleanup()
+        CCAiManager.stopPlay()
     }
     override func viewDidLoad() {
         super.viewDidLoad()
@@ -34,7 +33,7 @@ class DialogTranslateVC:BaseVC{
         navBar.rightView.setImage(UIImage(named: "help_icon"), for: .normal)
         
         toolBar.do { v in
-//            v.backgroundColor = .clear
+            //            v.backgroundColor = .clear
             v.delegate = self
             v.navBar = navBar
         }
@@ -98,8 +97,7 @@ class DialogTranslateVC:BaseVC{
     override func navleftViewTapped() {
         JoyGemSIManager.shared().stop()
         JoyGemSIDeviceManager.shared().stop()
-        PcmSound.sharedInstance().stopAudioUnit()
-        PcmSound.sharedInstance().cleanup()
+        CCAiManager.stopPlay()
         super.navleftViewTapped()
     }
     
@@ -112,7 +110,7 @@ class DialogTranslateVC:BaseVC{
     }
 }
 
-extension DialogTranslateVC:UITableViewDelegate,UITableViewDataSource {
+extension DcDialogTranslateVC:UITableViewDelegate,UITableViewDataSource {
     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
         return dataList.count
     }
@@ -127,7 +125,7 @@ extension DialogTranslateVC:UITableViewDelegate,UITableViewDataSource {
     }
 }
 
-extension DialogTranslateVC:DialogRecordBarDelegate{
+extension DcDialogTranslateVC:DialogRecordBarDelegate{
     func recordBar(_ bar: DialogRecordBar, layoutChange: DialogRecordLayoutType) {
         if  layoutChange == .spolit{
             splitView.isHidden = false
@@ -201,8 +199,8 @@ extension DialogTranslateVC:DialogRecordBarDelegate{
     }
 }
 
-extension DialogTranslateVC : JoyGemSIManagerDelegate {
-
+extension DcDialogTranslateVC : JoyGemSIManagerDelegate {
+    
     
     func siManagerStopped(_ manager: JoyGemSIManager, reason code: JoyGemRecordStopCode) {
         if let model = dataList.last,model.send.isEmpty{
@@ -277,8 +275,8 @@ extension DialogTranslateVC : JoyGemSIManagerDelegate {
     }
 }
 
-extension DialogTranslateVC : JoyGemSIDeviceManagerDelegate {
-
+extension DcDialogTranslateVC : JoyGemSIDeviceManagerDelegate {
+    
     
     
     func siDeviceManagerStopped(_ manager: JoyGemSIDeviceManager, reason code: JoyGemRecordStopCode) {
@@ -355,7 +353,7 @@ extension DialogTranslateVC : JoyGemSIDeviceManagerDelegate {
 }
 
 
-extension DialogTranslateVC : PcmSoundDelegate {
+extension DcDialogTranslateVC : PcmSoundDelegate {
     func remaining(_ frames: UInt) {
         Logger.debug("[frame:\(frames)]");
     }
diff --git a/ShenYin/ShenYin/Sdk/DcSdk/VC/DcStereoCallVC.swift b/ShenYin/ShenYin/Sdk/DcSdk/VC/DcStereoCallVC.swift
new file mode 100644 (file)
index 0000000..6fa9e3f
--- /dev/null
@@ -0,0 +1,264 @@
+//
+//  Untitled 2.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/12/29.
+//
+
+import UIKit
+import AVFAudio
+class DcStereoCallVC:BaseVC{
+    let toolBar = FieldRecordBar()
+    let line = UIView()
+    let selfTextView = UITextView()
+    let otherTextView = UITextView()
+    private let selfLabel = DotLabel()
+    private let otherLabel = DotLabel()
+    let rhythView = RhythView()
+    
+    
+    var autoStart = false
+    var needSave = true
+    var bgEnter = false//是否是APP在后台进入这个页面的
+    
+    init(autoStart :Bool = false) {
+        self.autoStart = autoStart
+        super.init()
+        bgEnter = App.shared.isInBackground
+        setupSdk()
+    }
+    override func viewDidLoad() {
+        super.viewDidLoad()
+    }
+    override func setupViews() {
+        navBar.title = "通话录音".l10n
+        navBar.backgroundColor = .white
+        
+        
+        toolBar.do { v in
+            v.delegate = self
+            v.hasRightItem = false
+            v.leftItem.language = AiLanguageCache.get(type: .thly) ?? .chinse
+            //            v.backgroundColor = .red
+        }
+        
+        
+        view.backgroundColor = .white
+        //        rhythView.backgroundColor = .blue
+        selfTextView.do { v in
+            v.backgroundColor = .white
+            v.isEditable = false
+            v.font = .regular(14)
+            v.text = ""
+            v.textColor = .hexString("#121212")
+            v.contentInset = UIEdgeInsets(top: 0, left: 13, bottom: 0, right: 13)
+            //            v.backgroundColor = .green
+        }
+        otherTextView.do { v in
+            v.backgroundColor = .white
+            v.isEditable = false
+            v.font = .regular(14)
+            v.text = ""
+            v.textColor = .hexString("#121212")
+            v.contentInset = UIEdgeInsets(top: 0, left: 13, bottom: 0, right: 13)
+            //            v.backgroundColor = .yellow
+        }
+        line.do { v in
+            v.backgroundColor = .hexString("#E9E9E9")
+        }
+        selfLabel.do { v in
+            v.label.text = "你:"
+            v.label.textColor = .hexString("#B9B9B9")
+            v.dot.backgroundColor = v.label.textColor
+            //            v.backgroundColor = .red
+        }
+        otherLabel.do { v in
+            v.label.text = "对方:"
+            v.label.textColor = .hexString("#0092E1")
+            v.dot.backgroundColor = v.label.textColor
+            //            v.backgroundColor = .red
+        }
+        view.addSubviews([rhythView,selfTextView,otherTextView,toolBar,line,selfLabel,otherLabel])
+        
+    }
+    override func setupLayout() {
+        rhythView.snp.makeConstraints { make in
+            make.width.centerX.equalToSuperview()
+            make.top.equalToSuperview().inset(kNavBtm)
+            make.height.equalTo(110)
+        }
+        toolBar.snp.makeConstraints { make in
+            make.width.centerX.equalToSuperview()
+            make.height.equalTo(120)
+            make.bottom.equalToSuperview().inset(20)
+        }
+        selfLabel.snp.makeConstraints { make in
+            make.height.equalTo(16)
+            make.left.right.equalToSuperview()
+            make.top.equalToSuperview().inset(kNavBtm + 120)
+        }
+        selfTextView.snp.makeConstraints { make in
+            make.top.equalTo(selfLabel.snp.bottom)
+            make.left.right.equalToSuperview()
+            make.height.equalTo((kScreenHei - kNavBtm - 120 - 140 - 25 - 32)/2)
+        }
+        line.snp.makeConstraints { make in
+            make.left.right.equalToSuperview().inset(36)
+            make.height.equalTo(1)
+            make.top.equalTo(selfTextView.snp.bottom).offset(12)
+        }
+        otherLabel.snp.makeConstraints { make in
+            make.height.equalTo(16)
+            make.left.right.equalToSuperview()
+            make.top.equalTo(line.snp.bottom).offset(12)
+        }
+        otherTextView.snp.makeConstraints { make in
+            make.height.equalTo(selfTextView)
+            make.left.right.equalToSuperview()
+            make.top.equalTo(otherLabel.snp.bottom)
+        }
+        
+    }
+    override func setupData() {
+        if bgEnter {
+            selfTextView.text = JoySdkManager.shared.callRecord.selfText
+            otherTextView.text = JoySdkManager.shared.callRecord.otherText
+        }
+    }
+    
+    func setupSdk(){
+        JoySdkManager.shared.callRecord.recordFinish = {[weak self] path in
+            guard let self = self else { return  }
+            if !needSave {
+                return
+            }
+            let v = PopTextField()
+            v.show()
+            v.cancelTap = {
+                App.shared.popPage()
+            }
+            v.okTap = { [weak self]  text in
+                guard let self = self else{return}
+                gotoDetail(fileName: text, wavPath: path, language: toolBar.leftItem.language)
+            }
+        }
+        JoySdkManager.shared.callRecord.stereoNeedReloadData = {[weak self] (text,index) in
+            guard let self = self else { return  }
+            if (index == 1) {
+                selfTextView.text = text
+                selfTextView.scrollToBottom()
+            } else if (index == 2) {
+                otherTextView.text = text
+                otherTextView.scrollToBottom()
+            }
+        }
+        JoySdkManager.shared.callRecord.callRecordAutoFinish = {[weak self]  in
+            guard let self = self else { return  }
+            stopAction()
+        }
+        JoySdkManager.shared.callRecord.callRecordNotOnCall = {[weak self]  in
+            guard let self = self else { return  }
+            YLprogressHUD.showAutoHiden("未处于通话状态".l10n)
+            rhythView.reset()
+            toolBar.recordBtn.isSelected = false
+            toolBar.leftItem.alpha = 1
+        }
+        if autoStart{
+            startRecord()
+        }
+    }
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = false
+    }
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = true
+    }
+    override func navleftViewTapped() {
+        if toolBar.recordBtn.isSelected {
+            CustomAlert.showWith(title: "提示".l10n, messgage: "当前正在录音,确定放弃当前录音吗?".l10n, cancelCallback: {}) {[unowned self] in
+                self.rhythView.stopTimer()
+                self.needSave = false
+                JoySdkManager.shared.callRecord.callRecordStop()
+                self.navigationController?.popViewController(animated: true)
+            }
+        }
+        else{
+            super.navleftViewTapped()
+        }
+    }
+    func startRecord(){
+        toolBar.recordBtnAction()
+    }
+    func stopRecord(){
+        toolBar.recordBtnAction()
+    }
+}
+
+extension DcStereoCallVC:FieldRecordBarDelegate{
+    func fieldRecordBar(_ bar: FieldRecordBar, languageChange language: CCAiLanguage) {
+        JoySdkManager.shared.callRecord.setCallRecoordLanage(language)
+        AiLanguageCache.save(type: .thly, lan: language)
+    }
+    
+    func fieldRecordBar(_ bar: FieldRecordBar, stateChange state: RecordSate) {
+        if state == .start {
+            //            selfMessageList.removeAll()
+            //            otherMessageList.removeAll()
+            selfTextView.text = nil
+            otherTextView.text = nil
+            JoySdkManager.shared.callRecord.callRecordStart()
+            rhythView.startTimer()
+        }
+        else if state == .stop{
+            stopAction()
+        }
+    }
+    func stopAction(){
+        rhythView.stopTimer()
+        JoySdkManager.shared.callRecord.callRecordStop()
+        toolBar.startAnimating()
+    }
+    func gotoDetail( fileName:String,wavPath:String,language:CCAiLanguage){
+        let model = RecordDetailModel()
+        model.type = .phone
+        model.from = language
+        model.to = .english
+        model.name = fileName
+        model.path = wavPath
+        model.duration = YLToos.getDuration(model.path)
+        model.origin = "你:".l10n + "\n" + selfTextView.text + "\n\n" + "对方:".l10n + "\n" + otherTextView.text
+        model.translate = ""
+        App.shared.popPage(animated: false)
+        App.shared.goToPage(RecordDetailVC(record: model))
+    }
+}
+
+fileprivate class DotLabel:BaseView{
+    let dot = UIView()
+    let label = UILabel()
+    
+    override func setupViews() {
+        addSubviews([dot,label])
+        dot.do { v in
+            v.yc.setCornerRadius(3)
+            v.backgroundColor = .hexString("#B9B9B9")
+        }
+        label.do { v in
+            v.font = .regular(12)
+        }
+    }
+    override func setupLayout() {
+        dot.snp.makeConstraints { make in
+            make.size.equalTo(6)
+            make.left.equalToSuperview().inset(18)
+            make.centerY.equalToSuperview()
+        }
+        label.snp.makeConstraints { make in
+            make.left.equalToSuperview().inset(28)
+            make.centerY.equalToSuperview()
+            make.right.equalToSuperview().inset(36)
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/Sdk/DcSdk/VC/DcTctyVC.swift b/ShenYin/ShenYin/Sdk/DcSdk/VC/DcTctyVC.swift
new file mode 100644 (file)
index 0000000..43fafb7
--- /dev/null
@@ -0,0 +1,155 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/12/29.
+//
+
+import UIKit
+import AVFoundation
+class DcTctyVC:BaseVC{
+    let toolBar = RecordBar()
+    let textView = PlaceHolderTextView()
+    let translateTextView = PlaceHolderTextView()
+    let sdk = DcTcty()
+    
+    deinit {
+        CCAiManager.stopPlay()
+    }
+    override func setupViews() {
+        view.addSubviews([toolBar,textView,translateTextView])
+        navBar.title = "同传听译".l10n
+        navBar.rightView.isHidden = false
+        navBar.rightView.setImage(UIImage(named: "help_icon"), for: .normal)
+        
+        toolBar.do { v in
+            v.backgroundColor = .clear
+            v.delegate = self
+            v.recordBtn.activeView.backgroundColor = .hexString("#F7F7F7")
+            v.leftItem.valueLabel.text = "手机".l10n
+            v.leftItem.language = AiLanguageCache.get(type: .tscy_left) ?? .chinse
+            v.rightItem.language = AiLanguageCache.get(type: .tscy_right) ?? .english
+        }
+        view.backgroundColor = .hexString("#F7F7F7")
+        
+        textView.do { v in
+            v.placeHolde = "此处将显示原文".l10n
+            v.textView.textColor = .hexString("#666666")
+        }
+        translateTextView.do { v in
+            v.textView.textColor = .hexString("#121212")
+            v.placeHolde = "此处将显示译文".l10n
+            v.bgView.yc.addgradientLayer(colors: [.hexString("#0092E1"),.hexString("#F7F7F7")], direction: .top2down,rect: CGRect(x: 0, y: 0, width: kScreenWid, height: (kScreenHei - kNavBtm - 120 - 10)/2))
+            v.bgView.alpha = 0.1
+            v.yc.setCorners(withRoundedRect: CGRect(x: 0, y: 0, width: kScreenWid, height: (kScreenHei - kNavBtm - 120 - 10)/2), roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 20, height: 20))
+        }
+    }
+    override func setupLayout() {
+        toolBar.snp.makeConstraints { make in
+            make.width.centerX.equalToSuperview()
+            make.height.equalTo(120)
+            make.bottom.equalToSuperview().inset(20)
+        }
+        textView.snp.makeConstraints { make in
+            make.top.equalToSuperview().inset(80)
+            make.left.right.equalToSuperview()
+            make.height.equalTo((kScreenHei - kNavBtm - 120 - 10)/2)
+        }
+        translateTextView.snp.makeConstraints { make in
+            make.top.equalTo(textView.snp.bottom).offset(10)
+            make.left.right.equalToSuperview()
+            make.bottom.equalTo(toolBar.snp.top)
+        }
+    }
+    override func setupData() {
+        if TipsCache.needShow(type: .tongchuan) {
+            RecordTipsView(type: .tongchuan).show()
+        }
+        
+        sdk.stopCallback = {[weak self] in
+            guard let self = self else{return}
+            toolBar.recordBtn.isSelected = false
+        }
+        sdk.refreshOriginCallback = {[weak self] text in
+            guard let self = self else{return}
+            textView.text = text
+            textView.textView.scrollToBottom()
+        }
+        sdk.refreshTranslateCallback = {[weak self] text in
+            guard let self = self else{return}
+            translateTextView.text = text
+            translateTextView.textView.scrollToBottom()
+        }
+        sdk.filePathCallback = { [weak self] path in
+            guard let self = self else{return}
+            guard let wavPath = DCFolder.recordFileResave(srcPath: path) else{return}
+            let v = PopTextField()
+            v.show()
+            v.cancelTap = {
+                App.shared.popPage()
+            }
+            v.okTap = { [weak self]  text in
+                guard let self = self else{return}
+                
+                let model = RecordDetailModel()
+                model.type = .translate
+                model.from = toolBar.leftItem.language
+                model.to = toolBar.rightItem.language
+                model.name = text
+                model.path = wavPath
+                model.duration = YLToos.getDuration(model.path)
+                model.origin = textView.text ?? ""
+                model.translate = translateTextView.text ?? ""
+                App.shared.popPage(animated: false)
+                App.shared.goToPage(RecordDetailVC(record: model))
+            }
+        }
+    }
+    override func navRightViewTapped() {
+        let tip = RecordTipsView(type: .tongchuan)
+        tip.show()
+    }
+    
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = false
+        
+        PcmSound.sharedInstance().delegate = sdk
+        CCAiManager.preparePlayBlueTooth()
+    }
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = true
+        CCAiManager.stopPlay()
+    }
+    override func navleftViewTapped() {
+        if toolBar.recordBtn.isSelected {
+            CustomAlert.showWith(title: "提示".l10n, messgage: "当前正在录音,确定放弃当前录音吗?".l10n, cancelCallback: {}) {
+                self.sdk.stop()
+                CCAiManager.stopPlay()
+                super.navleftViewTapped()
+            }
+        }
+        else{
+            super.navleftViewTapped()
+        }
+    }
+}
+
+extension DcTctyVC:RecordBarDelegate{
+    func recordBarLanguageChange(_ bar:RecordBar){
+        AiLanguageCache.save(type: .tscy_left, lan: bar.leftItem.language)
+        AiLanguageCache.save(type: .tscy_right, lan: bar.rightItem.language)
+    }
+    func recordBar(_ bar: RecordBar, stateChange state: RecordBarSate) {
+        if state == .start {
+            textView.text = nil
+            translateTextView.text = nil
+            sdk.start(from: toolBar.leftItem.language.value, to: toolBar.rightItem.language.value)
+        }
+        else if state == .stop{
+            sdk.stop()
+            bar.startAnimating()
+        }
+    }
+}
diff --git a/ShenYin/ShenYin/Sdk/DcSdk/VC/DcXclyVC.swift b/ShenYin/ShenYin/Sdk/DcSdk/VC/DcXclyVC.swift
new file mode 100644 (file)
index 0000000..ae9c8e1
--- /dev/null
@@ -0,0 +1,176 @@
+//
+//  Untitled.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/12/26.
+//
+
+//
+//  FieldRecordingVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/8/6.
+//
+
+import UIKit
+import AVFAudio
+class DcXclyVC:BaseVC{
+    let toolBar = FieldRecordBar()
+    let textView = UITextView()
+    let rhythView = RhythView()
+    
+    var autoStart = false
+    var needSave = true
+    var bgEnter = false//是否是APP在后台进入这个页面的
+    
+    init(autoStart :Bool = false) {
+        self.autoStart = autoStart
+        super.init()
+        bgEnter = App.shared.isInBackground
+        setupSdk()
+    }
+    
+    override func setupViews() {
+        navBar.title = "现场录音".l10n
+        navBar.backgroundColor = .white
+        toolBar.do { v in
+            v.delegate = self
+            v.leftItem.language = AiLanguageCache.get(type: .xcly) ?? .chinse
+        }
+        view.backgroundColor = .white
+        textView.do { v in
+            v.isEditable = false
+            v.font = .regular(14)
+            v.text = ""
+            v.backgroundColor = .white
+            v.textColor = .hexString("#121212")
+            v.contentInset = UIEdgeInsets(top: 0, left: 18, bottom: 0, right: 18)
+        }
+        view.addSubviews([rhythView,textView,toolBar])
+    }
+    override func setupLayout() {
+        rhythView.snp.makeConstraints { make in
+            make.width.centerX.equalToSuperview()
+            make.top.equalToSuperview().inset(kNavBtm)
+            make.height.equalTo(110)
+        }
+        toolBar.snp.makeConstraints { make in
+            make.width.centerX.equalToSuperview()
+            make.height.equalTo(120)
+            make.bottom.equalToSuperview().inset(20)
+        }
+        textView.snp.makeConstraints { make in
+            make.top.equalTo(rhythView.snp.bottom)
+            make.left.right.equalToSuperview()
+            make.bottom.equalTo(toolBar.snp.top)
+        }
+    }
+    
+    override func setupData() {
+        if bgEnter {
+            textView.text = DcXcly.shared.recordText
+        }
+    }
+    
+    func setupSdk() {
+        DcXcly.shared.recordFinish = {[weak self] path in
+            guard let self = self else { return  }
+            if !needSave {
+                return
+            }
+            let v = PopTextField()
+            v.show()
+            v.cancelTap = {
+                App.shared.popPage()
+            }
+            v.okTap = { [weak self]  text in
+                guard let self = self else{return}
+                Logger.debug("111111 ===",textView.text ?? "")
+                let model = RecordDetailModel()
+                model.type = .present
+                model.from = toolBar.leftItem.language
+                model.to = .english
+                model.name = text
+                model.path = path
+                model.duration = YLToos.getDuration(model.path)
+                model.origin = textView.text
+                model.translate = ""
+                App.shared.popPage(animated: false)
+                App.shared.goToPage(RecordDetailVC(record: model))
+            }
+        }
+        DcXcly.shared.recordNeedReloadData = {[weak self] text in
+            guard let self = self else { return  }
+            textView.text = text
+            textView.scrollToBottom()
+        }
+//        DcXcly.shared.codeCallback = {[weak self] code in
+//            guard let code = code else{return}
+//            if code == 20001 {
+//                self.recordBtn.isSelected = true
+//            }
+//            else if code == 20002 || code == 20003{
+//                self.recordBtn.isSelected = false
+//            }
+//        }
+        if autoStart{
+            startRecord()
+        }
+    }
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = false
+    }
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = true
+    }
+    override func navleftViewTapped() {
+        if toolBar.recordBtn.isSelected {
+            CustomAlert.showWith(title: "提示", messgage: "当前正在录音,确定放弃当前录音吗?".l10n, cancelCallback: {}) {[unowned self] in
+                self.needSave = false
+                self.rhythView.stopTimer()
+                DcXcly.shared.recordStop()
+                self.navigationController?.popViewController(animated: true)
+            }
+        }
+        else{
+            super.navleftViewTapped()
+        }
+    }
+    func startRecord(){
+        toolBar.recordBtnAction()
+        rhythView.startTimer()
+    }
+    func stopRecord(){
+        toolBar.recordBtnAction()
+        rhythView.stopTimer()
+    }
+}
+
+extension DcXclyVC:FieldRecordBarDelegate{
+    func fieldRecordBar(_ bar: FieldRecordBar, languageChange language: CCAiLanguage) {
+        DcXcly.shared.setRecoordLanage(language)
+        AiLanguageCache.save(type: .xcly, lan: language)
+    }
+    
+    func fieldRecordBar(_ bar: FieldRecordBar, stateChange state: RecordSate) {
+        if state == .start {
+            DcXcly.shared.recordStart()
+            rhythView.startTimer()
+        }
+        if state == .resume{
+            rhythView.startTimer()
+            DcXcly.shared.recordResume()
+        }
+        else if state == .pause{
+            DcXcly.shared.recordPause()
+            rhythView.stopTimer()
+        }
+        else if state == .stop{
+            rhythView.stopTimer()
+            DcXcly.shared.recordStop()
+            bar.startAnimating()
+        }
+    }
+}
index 2dbac9c..08479bd 100644 (file)
@@ -34,7 +34,7 @@ extension JoyChat : JoyGemChatManagerDelegate {
             isTtsPlaying = true
             Logger.debug("开始播放")
             JoyGemChatManager.shared().playAudioStart()
-            App.shared.preparePlay()
+            CCAiManager.preparePlayBlueTooth()
         }
         Logger.debug("收到音频数据", data?.count ?? 0)
         PcmSound.sharedInstance().feed(data)
@@ -47,7 +47,7 @@ extension JoyChat : JoyGemChatManagerDelegate {
         case .startRecord:
             if (isTtsPlaying) {
                 isTtsPlaying = false
-                App.shared.preparePlay()
+                CCAiManager.preparePlayBlueTooth()
             }
             break
         case .stopRecord:
diff --git a/ShenYin/ShenYin/Sdk/JoySdk/JoyChinse2English.swift b/ShenYin/ShenYin/Sdk/JoySdk/JoyChinse2English.swift
new file mode 100644 (file)
index 0000000..f8a93f1
--- /dev/null
@@ -0,0 +1,104 @@
+//
+//  JoyChinse2English.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/12/26.
+//
+import JoyGemAI
+
+class JoyChinse2English:NSObject{
+    var dataList:[(Chinese2EnglishCellModel,Int)] = []
+    var startIndex = 0//记录开始录音的次数,用于区分返回的数据
+    
+    var stopCallback:(()->Void)?
+    var refreshDataCallback:(()->Void)?
+    override init(){
+        super.init()
+        JoyGemSIManager.shared().delegate = self
+        PcmSound.sharedInstance().delegate = self
+    }
+    
+    func start(){
+        startIndex += 100000
+        //            JoyGemSIManager.shared().originalLanguage = bar.leftItem.language.value
+        //            JoyGemSIManager.shared().targetLanguage = bar.rightItem.language.value
+        JoyGemSIManager.shared().start()
+        //            do {
+        //                try AVAudioSession.sharedInstance().setCategory(.playAndRecord, options: [.defaultToSpeaker])
+        //                try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
+        //            } catch {
+        //                print(error)
+        //            }
+    }
+
+    func stop(){
+        JoyGemSIManager.shared().stop()
+    }
+    
+}
+extension JoyChinse2English : JoyGemSIManagerDelegate {
+    func siManagerStopped(_ manager: JoyGemSIManager, reason code: JoyGemRecordStopCode) {
+        stopCallback?()
+        Logger.debug("停止",code.rawValue)
+//        if code == .passively {
+//            toolBar.recordBtn.isSelected = false
+//        }
+    }
+    
+    func siManager(_ manager: JoyGemSIManager, didUpdateRaw model: JoyGemAsrModel) {
+        Logger.debug("text: \(model.text),index: \(model.index)")
+        let msg = dataList.firstIndex (where: { (str,idx) in
+            return idx == Int(model.index) + startIndex
+        })
+        if (msg == nil) {
+            dataList.append((Chinese2EnglishCellModel(send: model.text),Int(model.index) + startIndex))
+        } else {
+            let (item,_) = dataList[msg!]
+            item.send =  model.text
+        }
+        //
+        //        ///监听到一句话结束,自动结束当前录音
+        //        if (model.definite == true) {
+        //            stopEarRecord()
+        //        }
+        refreshDataCallback?()
+    }
+    
+    func siManager(_ manager: JoyGemSIManager, didUpdateTranslate model: JoyGemAsrModel) {
+        let msg = dataList.firstIndex (where: { (str,idx) in
+            return idx == Int(model.index) + startIndex
+        })
+        if (msg == nil) {
+            dataList.append((Chinese2EnglishCellModel(response: model.text),Int(model.index) + startIndex))
+        } else {
+            let (item,_) = dataList[msg!]
+            item.response =  model.text
+        }
+        refreshDataCallback?()
+    }
+    
+    func siManager(_ manager: JoyGemSIManager, saveFile path: String) {
+        Logger.debug("path: \(path)")
+    }
+    
+    
+    func siManager(_ manager: JoyGemSIManager, stateChange code: JoyGemStateCode) {
+        Logger.debug("code: \(code)")
+    }
+    
+    func siManagerStopped(_ manager: JoyGemSIManager) {
+        
+    }
+    func siManager(_ manager: JoyGemSIManager, didUpdateAudioData data: Data, sampleRate: UInt) {
+        PcmSound.sharedInstance().feed(data)
+    }
+    
+    func siManager(_ manager: JoyGemSIManager, onError code: JoyGemErrorCode) {
+        
+    }
+}
+extension JoyChinse2English:PcmSoundDelegate{
+    func remaining(_ frames: UInt) {
+        
+    }
+}
diff --git a/ShenYin/ShenYin/Sdk/JoySdk/JoyDialog.swift b/ShenYin/ShenYin/Sdk/JoySdk/JoyDialog.swift
new file mode 100644 (file)
index 0000000..c640165
--- /dev/null
@@ -0,0 +1,16 @@
+//
+//  JoyDialog.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/12/29.
+//
+
+class JoyDialog: NSObject {
+    
+    
+    
+    
+    
+    
+    
+}
index 4149aa3..4e9a702 100644 (file)
@@ -116,7 +116,7 @@ extension JoySdkManager : JoyGemAIManagerDelegate {
     }
     func joyGemAuthSuccessCallback() {
         Logger.debug("鉴权成功")
-        App.shared.preparePlay()
+        CCAiManager.preparePlayBlueTooth()
         
         DeviceCache.saveLastConnected(mac: CCBleManager.shared.connectPeripheral!.mac)
         DeviceCache.shared.add(CCBleManager.shared.connectPeripheral!.device.identifier.uuidString, mac: CCBleManager.shared.connectPeripheral!.mac)
@@ -134,40 +134,40 @@ extension JoySdkManager : JoyGemAIManagerDelegate {
         switch action {
         case .cancel:
             Logger.debug("请求关闭录音功能/通知关闭了对话")
-            if let topVC = App.shared.topVC(), let vc = topVC as? FieldRecordingVC {
+            if let topVC = App.shared.topVC(), let vc = topVC as? JoyXclyVC {
                 vc.stopRecord()
             }
-            if let topVC = App.shared.topVC(), let vc = topVC as? CallingRecordVC {
+            if let topVC = App.shared.topVC(), let vc = topVC as? JoyCallingRecordVC {
                 vc.stopRecord()
             }
-            if let topVC = App.shared.topVC(), let vc = topVC as? StereoCallVC {
+            if let topVC = App.shared.topVC(), let vc = topVC as? JoyStereoCallVC {
                 vc.stopRecord()
             }
             JoySdkManager.shared.record.recordStop()
         case .recordLive:
             Logger.debug("请求开启现场录音")
-            if let vc = App.shared.topVC() as? FieldRecordingVC {
+            if let vc = App.shared.topVC() as? JoyXclyVC {
                 vc.startRecord()
             }
             else{
-                App.shared.goToPage(FieldRecordingVC(autoStart: true))
+                App.shared.goToPage(JoyXclyVC(autoStart: true))
                 JoySdkManager.shared.record.recordStart()
             }
         case .recordCall:
             Logger.debug("请求开启通话录音")
             if JoySdkManager.shared.callRecord.supportStereo{
-                if let vc = App.shared.topVC() as? StereoCallVC {
+                if let vc = App.shared.topVC() as? JoyStereoCallVC {
                     vc.startRecord()
                 }else{
-                    App.shared.goToPage(CallingRecordVC(autoStart: true))
+                    App.shared.goToPage(JoyCallingRecordVC(autoStart: true))
                     JoySdkManager.shared.callRecord.callRecordStart()
                 }
             }
             else{
-                if let vc = App.shared.topVC() as? CallingRecordVC {
+                if let vc = App.shared.topVC() as? JoyCallingRecordVC {
                     vc.startRecord()
                 }else{
-                    App.shared.goToPage(CallingRecordVC(autoStart: true))
+                    App.shared.goToPage(JoyCallingRecordVC(autoStart: true))
                     JoySdkManager.shared.callRecord.callRecordStart()
                 }
             }
@@ -176,8 +176,8 @@ extension JoySdkManager : JoyGemAIManagerDelegate {
             Logger.debug( "请求开启同传听译")
         case .chat:
             Logger.debug("通知开启AI对话")
-            if !App.shared.topVC().isKind(of: ChatVC.self) {
-                App.shared.goToPage(ChatVC())
+            if !App.shared.topVC().isKind(of: JoyChatVC.self) {
+                App.shared.goToPage(JoyChatVC())
             }
         @unknown default:
             break
diff --git a/ShenYin/ShenYin/Sdk/JoySdk/JoyTcty.swift b/ShenYin/ShenYin/Sdk/JoySdk/JoyTcty.swift
new file mode 100644 (file)
index 0000000..368ae4c
--- /dev/null
@@ -0,0 +1,120 @@
+//
+//  JoyTcty.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/12/29.
+//
+
+import JoyGemAI
+
+class JoyTcty:NSObject{
+    var messageList : [(String,UInt)] = []
+    var translateList : [(String,UInt)] = []
+    var recordFilePath : String?
+
+    
+    var stopCallback:(()->Void)?
+    var refreshOriginCallback:((String)->Void)?
+    var refreshTranslateCallback:((String)->Void)?
+    var filePathCallback:((String)->Void)?
+    
+    override init(){
+        super.init()
+        JoyGemSIManager.shared().delegate = self
+        PcmSound.sharedInstance().delegate = self
+    }
+    
+    func start(from:String,to:String){
+        messageList.removeAll()
+        translateList.removeAll()
+        
+        if Thread.isMainThread {
+            do {
+                try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [])
+                try AVAudioSession.sharedInstance().setActive(true)
+            } catch let error as NSError {
+                Logger.debug("An error occurred setting the audio session category: \(error.localizedDescription)")
+            }
+        } else {
+            DispatchQueue.main.sync {
+                do {
+                    try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [])
+                    try AVAudioSession.sharedInstance().setActive(true)
+                } catch let error as NSError {
+                    Logger.debug("An error occurred setting the audio session category: \(error.localizedDescription)")
+                }
+            }
+        }
+        JoyGemSIManager.shared().originalLanguage = from
+        JoyGemSIManager.shared().targetLanguage = to
+        JoyGemSIManager.shared().start()
+    }
+    
+    func stop(){
+        JoyGemSIManager.shared().stop()
+    }
+    
+}
+
+extension JoyTcty : JoyGemSIManagerDelegate {
+    func siManagerStopped(_ manager: JoyGemSIManager, reason code: JoyGemRecordStopCode) {
+        stopCallback?()
+    }
+    
+    func siManager(_ manager: JoyGemSIManager, didUpdateRaw model: JoyGemAsrModel) {
+        let msg = messageList.firstIndex (where: { (str,idx) in
+            return idx == model.index;
+        })
+        if (msg == nil) {
+            messageList.append((model.text,model.index))
+        } else {
+            messageList[msg!] = (model.text,model.index)
+        }
+        
+        let text = messageList.compactMap({ (str,idx) in
+            return str
+        }).joined(separator: "\n")
+        refreshOriginCallback?(text)
+    }
+    
+    func siManager(_ manager: JoyGemSIManager, didUpdateTranslate model: JoyGemAsrModel) {
+        Logger.debug("translate text: \(model.text),index: \(model.index)")
+        let msg = translateList.firstIndex (where: { (str,idx) in
+            return idx == model.index;
+        })
+        if (msg == nil) {
+            translateList.append((model.text,model.index))
+        } else {
+            translateList[msg!] = (model.text,model.index)
+        }
+        
+        let text = translateList.compactMap({ (str,idx) in
+            return str
+        }).joined(separator: "\n")
+        refreshTranslateCallback?(text)
+    }
+    
+    
+    func siManager(_ manager: JoyGemSIManager, saveFile path: String) {
+        Logger.debug("path: \(path)")
+        recordFilePath = path
+        filePathCallback?(path)
+    }
+    
+    func siManager(_ manager: JoyGemSIManager, stateChange code: JoyGemStateCode) {
+        Logger.debug("code: \(code)")
+    }
+    
+    func siManager(_ manager: JoyGemSIManager, didUpdateAudioData data: Data, sampleRate: UInt) {
+        PcmSound.sharedInstance().feed(data)
+    }
+    func siManager(_ manager: JoyGemSIManager, onError code: JoyGemErrorCode) {
+        
+    }
+}
+
+extension JoyTcty : PcmSoundDelegate {
+    func remaining(_ frames: UInt) {
+        Logger.debug("[frame:\(frames)]");
+    }
+}
@@ -1,5 +1,5 @@
 //
-//  CallingRecordVC.swift
+//  JoyCallingRecordVC.swift
 //  ShenYin
 //
 //  Created by liangshi on 2025/8/6.
@@ -7,7 +7,7 @@
 
 import UIKit
 import AVFAudio
-class CallingRecordVC:BaseVC{
+class JoyCallingRecordVC:BaseVC{
     let toolBar = FieldRecordBar()
     let textView = UITextView()
     let rhythView = RhythView()
@@ -151,7 +151,7 @@ class CallingRecordVC:BaseVC{
     }
 }
 
-extension CallingRecordVC:FieldRecordBarDelegate{
+extension JoyCallingRecordVC:FieldRecordBarDelegate{
     func fieldRecordBar(_ bar: FieldRecordBar, languageChange language: CCAiLanguage) {
         JoySdkManager.shared.callRecord.setCallRecoordLanage(language)
         AiLanguageCache.save(type: .thly, lan: language)
similarity index 88%
rename from ShenYin/ShenYin/UI/Home/Chat/ChatVC.swift
rename to ShenYin/ShenYin/Sdk/JoySdk/VC/JoyChatVC.swift
index 0b63f91..ee53a3f 100644 (file)
@@ -8,7 +8,7 @@
 import UIKit
 import JoyGemAI
 import AVFAudio
-class ChatVC:BaseVC{
+class JoyChatVC:BaseVC{
     let rightItem = ChatNavigationBarItem(frame: CGRect(x: 0, y: 0, width: 80, height: 44))
     let roleItem = ChatRoleItem(frame: CGRect(x: 0, y: 0, width: 100, height: 44))
     
@@ -17,11 +17,10 @@ class ChatVC:BaseVC{
     let headerView = ChatHeaderView(frame: CGRect(x: 0, y: 0, width: kScreenWid, height: 260))
     let tipsLabel = UILabel()
     
-    var roleList:[JoyGemSpeaker] = []
+    var roleList:[CCRole] = []
     
     deinit {
-        PcmSound.sharedInstance().stopAudioUnit()
-        PcmSound.sharedInstance().cleanup()
+        CCAiManager.stopPlay()
         JoyGemChatManager.shared().removeObserver(self, forKeyPath: "isActive")
     }
     
@@ -142,7 +141,12 @@ class ChatVC:BaseVC{
             guard let self = self else{return}
             roleItem.role = model
             headerView.update(name: model.name, role: model.role)
-            JoyGemChatManager.shared().setSpeaker(model)
+            let joyMode = JoyGemSpeaker()
+            joyMode.role = model.role
+            joyMode.sid = model.sid
+            joyMode.name = model.name
+            joyMode.type = model.type
+            JoyGemChatManager.shared().setSpeaker(joyMode)
         }
         App.shared.goToPage(vc)
     }
@@ -155,8 +159,8 @@ class ChatVC:BaseVC{
     func loadRoleList(){
         JoyGemChatManager.shared().getSpeakerList {[weak self] speakers in
             guard let self = self,speakers.count > 0 else{return}
-            self.roleList = speakers
-            self.roleList.insert(xiaoYinRole(), at: 0)
+            self.roleList = speakers.map{CCRole(sid: $0.sid, name: $0.name, type: $0.type, role: $0.role)}
+            self.roleList.insert(CCRole.xiaoYin, at: 0)
             
             var currentRole = roleList.first
             if let cacheRole = RoleCache.get(){
@@ -169,20 +173,17 @@ class ChatVC:BaseVC{
             }            
             self.roleItem.role = currentRole
             self.headerView.update(name: self.roleItem.role.name, role: self.roleItem.role.role)
-            JoyGemChatManager.shared().setSpeaker(self.roleItem.role)
+            let joyMode = JoyGemSpeaker()
+            joyMode.role = self.roleItem.role.role
+            joyMode.sid = self.roleItem.role.sid
+            joyMode.name = self.roleItem.role.name
+            joyMode.type = self.roleItem.role.type
+            JoyGemChatManager.shared().setSpeaker(joyMode)
         }
     }
-    func xiaoYinRole()->JoyGemSpeaker{
-        let model = JoyGemSpeaker()
-        model.sid = "zh_female_tianmeitaozi_mars_bigtts"
-        model.name = "小音"
-        model.type = "default"
-        model.role = "test_xiaoyin"
-        return model
-    }
 }
 
-extension ChatVC:ChatRecordBarDelegate{
+extension JoyChatVC:ChatRecordBarDelegate{
     func recordBarCancel(_ toolBar: ChatRecordBar) {
 //        JoyGemChatManager.shared().stopChat()
         JoyGemChatManager.shared().stopSingleChat()
@@ -209,7 +210,7 @@ extension ChatVC:ChatRecordBarDelegate{
     }
 }
 
-extension ChatVC:UITableViewDelegate,UITableViewDataSource {
+extension JoyChatVC:UITableViewDelegate,UITableViewDataSource {
     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
         return JoySdkManager.shared.chat.dataList.count
     }
@@ -6,15 +6,13 @@
 //
 
 import UIKit
-import JoyGemAI
 import AVFAudio
-class Chinese2EnglishVC:BaseVC{
+class JoyChinese2EnglishVC:BaseVC{
     let toolBar = RecordBar()
     let tableView = UITableView(frame: CGRectZero, style: .plain)
     let bg = UIImageView()
+    let sdk = JoyChinse2English()
     var dataList:[(Chinese2EnglishCellModel,Int)] = []
-    var recordFilePath : String?
-    var startIndex = 0//记录开始录音的次数,用于区分返回的数据
 
 //    deinit {
 //        PcmSound.sharedInstance().stopAudioUnit()
@@ -60,32 +58,40 @@ class Chinese2EnglishVC:BaseVC{
         }
     }
     override func setupData() {
-        JoyGemSIManager.shared().delegate = self
-
         if TipsCache.needShow(type: .chinese2english) {
             RecordTipsView(type: .chinese2english).show()
         }
+        sdk.stopCallback = {[weak self] in
+            guard let self = self else{return}
+            toolBar.recordBtn.isSelected = false
+        }
+        sdk.refreshDataCallback = {[weak self] in
+            guard let self = self else{return}
+            dataList = sdk.dataList
+            refreshData()
+        }
     }
     override func viewDidAppear(_ animated: Bool) {
         super.viewDidAppear(animated)
         navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = false
         
-        PcmSound.sharedInstance().delegate = self;
-        App.shared.preparePlay()
+        PcmSound.sharedInstance().delegate = sdk
+        CCAiManager.preparePlayBlueTooth()
     }
     override func viewDidDisappear(_ animated: Bool) {
         super.viewDidDisappear(animated)
         navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = true
-        App.shared.preparePlay()
+        CCAiManager.preparePlayBlueTooth()
     }
     override func navleftViewTapped() {
         if toolBar.recordBtn.isSelected {
-            CustomAlert.showWith(title: "提示".l10n, messgage: "当前正在录音,确定放弃当前录音吗".l10n, cancelCallback: {}) {
-//                JoyGemSIManager.shared().stop()
-//                PcmSound.sharedInstance().stopAudioUnit()
-//                PcmSound.sharedInstance().cleanup()
-                super.navleftViewTapped()
-            }
+            self.sdk.stop()
+//            CustomAlert.showWith(title: "提示".l10n, messgage: "当前正在录音,确定放弃当前录音吗".l10n, cancelCallback: {}) {
+//                self.sdk.stop()
+////                PcmSound.sharedInstance().stopAudioUnit()
+////                PcmSound.sharedInstance().cleanup()
+//                super.navleftViewTapped()
+//            }
         }
         else{
             super.navleftViewTapped()
@@ -106,36 +112,27 @@ class Chinese2EnglishVC:BaseVC{
         }
     }
 }
-extension Chinese2EnglishVC:RecordBarDelegate{
+extension JoyChinese2EnglishVC:RecordBarDelegate{
     func recordBarLanguageChange(_ bar: RecordBar) {
         
     }
     func recordBar(_ bar: RecordBar, stateChange state: RecordBarSate) {
         if state == .start {
-            startIndex += 100000
-//            JoyGemSIManager.shared().originalLanguage = bar.leftItem.language.value
-//            JoyGemSIManager.shared().targetLanguage = bar.rightItem.language.value
-            JoyGemSIManager.shared().start()
-//            do {
-//                try AVAudioSession.sharedInstance().setCategory(.playAndRecord, options: [.defaultToSpeaker])
-//                try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
-//            } catch {
-//                print(error)
-//            }
+            sdk.start()
         }
         else if state == .stop{
-            JoyGemSIManager.shared().stop()
+            sdk.stop()
         }
     }
 }
-extension Chinese2EnglishVC:UITableViewDelegate,UITableViewDataSource {
+extension JoyChinese2EnglishVC:UITableViewDelegate,UITableViewDataSource {
     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
         return dataList.count
     }
     
     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
         let cell = Chinese2EnglishCell.cell(withTableView: tableView, indexPath: indexPath)
-        let (vallue,index) = dataList[indexPath.row]
+        let (vallue,_) = dataList[indexPath.row]
         cell.data = vallue
         return cell
     }
@@ -143,70 +140,3 @@ extension Chinese2EnglishVC:UITableViewDelegate,UITableViewDataSource {
         
     }
 }
-
-extension Chinese2EnglishVC : JoyGemSIManagerDelegate {
-    func siManagerStopped(_ manager: JoyGemSIManager, reason code: JoyGemRecordStopCode) {
-        Logger.debug("停止",code.rawValue)
-        if code == .passively {
-            toolBar.recordBtn.isSelected = false
-        }
-    }
-    
-    func siManager(_ manager: JoyGemSIManager, didUpdateRaw model: JoyGemAsrModel) {
-        Logger.debug("text: \(model.text),index: \(model.index)")
-        let msg = dataList.firstIndex (where: { (str,idx) in
-            return idx == Int(model.index) + startIndex
-        })
-        if (msg == nil) {
-            dataList.append((Chinese2EnglishCellModel(send: model.text),Int(model.index) + startIndex))
-        } else {
-            let (item,_) = dataList[msg!]
-            item.send =  model.text
-        }
-        //
-        //        ///监听到一句话结束,自动结束当前录音
-        //        if (model.definite == true) {
-        //            stopEarRecord()
-        //        }
-        refreshData()
-    }
-    
-    func siManager(_ manager: JoyGemSIManager, didUpdateTranslate model: JoyGemAsrModel) {
-        let msg = dataList.firstIndex (where: { (str,idx) in
-            return idx == Int(model.index) + startIndex
-        })
-        if (msg == nil) {
-            dataList.append((Chinese2EnglishCellModel(response: model.text),Int(model.index) + startIndex))
-        } else {
-            let (item,_) = dataList[msg!]
-            item.response =  model.text
-        }
-        refreshData()
-    }
-    
-    func siManager(_ manager: JoyGemSIManager, saveFile path: String) {
-        Logger.debug("path: \(path)")
-        recordFilePath = path
-    }
-    
-    
-    func siManager(_ manager: JoyGemSIManager, stateChange code: JoyGemStateCode) {
-        Logger.debug("code: \(code)")
-    }
-    
-    func siManagerStopped(_ manager: JoyGemSIManager) {
-        
-    }
-    func siManager(_ manager: JoyGemSIManager, didUpdateAudioData data: Data, sampleRate: UInt) {
-        PcmSound.sharedInstance().feed(data)
-    }
-
-    func siManager(_ manager: JoyGemSIManager, onError code: JoyGemErrorCode) {
-        
-    }
-}
-extension Chinese2EnglishVC:PcmSoundDelegate{
-    func remaining(_ frames: UInt) {
-        
-    }
-}
diff --git a/ShenYin/ShenYin/Sdk/JoySdk/VC/JoyDialogTranlateVC.swift b/ShenYin/ShenYin/Sdk/JoySdk/VC/JoyDialogTranlateVC.swift
new file mode 100644 (file)
index 0000000..c81a8b2
--- /dev/null
@@ -0,0 +1,357 @@
+//
+//  JoyDialogTranslateVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/8/6.
+//
+
+import UIKit
+import JoyGemAI
+import AVFoundation
+class JoyDialogTranslateVC:BaseVC{
+    let toolBar = DialogRecordBar()
+    
+    let splitView = DialogSplitView()
+    let tableView = UITableView(frame: CGRectZero, style: .plain)
+    var dataList:[DialogTranslateCellModel] = []
+    
+    var tmpList : [(String,UInt)] = []
+    var tmpOtherList : [(String,UInt)] = []
+    deinit {
+        CCAiManager.stopPlay()
+    }
+    override func viewDidLoad() {
+        super.viewDidLoad()
+    }
+    override func setupViews() {
+        view.addSubviews([toolBar,tableView,splitView])
+        
+        navBar.title = "对话翻译".l10n
+        navBar.rightView.isHidden = false
+        navBar.rightView.setImage(UIImage(named: "help_icon"), for: .normal)
+        
+        toolBar.do { v in
+//            v.backgroundColor = .clear
+            v.delegate = self
+            v.navBar = navBar
+        }
+        view.backgroundColor = .hexString("#F7F7F7")
+        
+        tableView.do { v in
+            v.backgroundColor = .hexString("#F7F7F7")
+            v.delegate = self
+            v.dataSource = self
+            v.estimatedRowHeight = 60
+            v.separatorStyle = .none
+            v.register(DialogTranslateCell.self, forCellReuseIdentifier: DialogTranslateCell.reuseID)
+        }
+        splitView.isHidden = true
+        
+    }
+    override func setupLayout() {
+        toolBar.snp.makeConstraints { make in
+            make.width.centerX.equalToSuperview()
+            make.height.equalTo(120)
+            make.bottom.equalToSuperview()
+        }
+        tableView.snp.makeConstraints { make in
+            make.top.equalToSuperview().inset(kNavBtm)
+            make.left.equalToSuperview()
+            make.width.equalTo(kScreenWid)
+            make.bottom.equalTo(toolBar.snp.top)
+        }
+        splitView.snp.makeConstraints { make in
+            make.edges.equalTo(tableView)
+        }
+    }
+    override func setupData() {
+        JoyGemSIManager.shared().delegate = self
+        JoyGemSIDeviceManager.shared().delegate = self
+        
+        PcmSound.sharedInstance().delegate = self;
+        PcmSound.sharedInstance().setup(16000, withchannels: 1)
+        do {
+            try AVAudioSession.sharedInstance().setActive(true)
+        } catch {
+            print(error)
+        }
+        
+        if TipsCache.needShow(type: .dialog) {
+            RecordTipsView(type: .dialog).show()
+        }
+    }
+    override func navRightViewTapped() {
+        let tip = RecordTipsView(type: .dialog)
+        tip.show()
+    }
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = false
+    }
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = true
+    }
+    override func navleftViewTapped() {
+        JoyGemSIManager.shared().stop()
+        JoyGemSIDeviceManager.shared().stop()
+        CCAiManager.stopPlay()
+        super.navleftViewTapped()
+    }
+    
+    func refreshData(){
+        tableView.reloadData()
+        if dataList.count > 0 {
+            tableView.scrollToRow(at: IndexPath(row: dataList.count - 1, section: 0), at: .none, animated: true)
+        }
+        splitView.dataList = dataList
+    }
+}
+
+extension JoyDialogTranslateVC:UITableViewDelegate,UITableViewDataSource {
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return dataList.count
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = DialogTranslateCell.cell(withTableView: tableView, indexPath: indexPath)
+        cell.data = dataList[indexPath.row]
+        return cell
+    }
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        
+    }
+}
+
+extension JoyDialogTranslateVC:DialogRecordBarDelegate{
+    func recordBar(_ bar: DialogRecordBar, layoutChange: DialogRecordLayoutType) {
+        if  layoutChange == .spolit{
+            splitView.isHidden = false
+            splitView.dataList = dataList
+        }
+        else{
+            splitView.isHidden = true
+        }
+    }
+    
+    func recordBar(_ bar: DialogRecordBar, device: DialogRecordDeviceType, stateChange state: RecordBarSate) {
+        if device == .earphone {
+            if state == .start {
+                startEarRecord()
+            }
+            else{
+                stopEarRecord()
+            }
+        }
+        else if device == .phone{
+            if state == .start {
+                startPhoneRecord()
+            }
+            
+            else{
+                stopPhoneRecord()
+            }
+        }
+    }
+    @objc func startPhoneRecord() {
+        tmpList.removeAll()
+        tmpOtherList.removeAll()
+        let model = DialogTranslateCellModel()
+        model.isPhone = true
+        dataList.append(model)
+        refreshData()
+        
+        JoyGemSIManager.shared().originalLanguage = toolBar.languageView.rightItem.language.value
+        JoyGemSIManager.shared().targetLanguage = toolBar.languageView.leftItem.language.value
+        Logger.debug("源语言",JoyGemSIManager.shared().originalLanguage)
+        Logger.debug("目标语言",JoyGemSIManager.shared().targetLanguage)
+        JoyGemSIManager.shared().start()
+    }
+    
+    @objc func stopPhoneRecord() {
+        JoyGemSIManager.shared().stop()
+    }
+    
+    @objc func startEarRecord() {
+        tmpList.removeAll()
+        tmpOtherList.removeAll()
+        let model = DialogTranslateCellModel()
+        model.isPhone = false
+        dataList.append(model)
+        refreshData()
+        
+        JoyGemSIDeviceManager.shared().originalLanguage = toolBar.languageView.leftItem.language.value
+        JoyGemSIDeviceManager.shared().targetLanguage = toolBar.languageView.rightItem.language.value
+        JoyGemSIDeviceManager.shared().start()
+        do {
+            try AVAudioSession.sharedInstance().setCategory(.playAndRecord, options: [.defaultToSpeaker])
+            try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
+        } catch {
+            print(error)
+        }
+    }
+    
+    @objc func stopEarRecord() {
+        JoyGemSIDeviceManager.shared().stop()
+    }
+}
+
+extension JoyDialogTranslateVC : JoyGemSIManagerDelegate {
+
+    
+    func siManagerStopped(_ manager: JoyGemSIManager, reason code: JoyGemRecordStopCode) {
+        if let model = dataList.last,model.send.isEmpty{
+            dataList.removeLast()
+            refreshData()
+        }
+    }
+    
+    func siManager(_ manager: JoyGemSIManager, didUpdateRaw model: JoyGemAsrModel) {
+        let msg = tmpList.firstIndex (where: { (str,idx) in
+            return idx == model.index;
+        })
+        if (msg == nil) {
+            tmpList.append((model.text,model.index))
+        } else {
+            tmpList[msg!] = (model.text,model.index)
+        }
+        
+        ///监听到一句话结束,自动结束当前录音
+        if (model.definite == true) {
+            toolBar.stopRecord(.phone)
+            stopPhoneRecord()
+        }
+        
+        guard let model = dataList.last else {
+            return
+        }
+        
+        model.send = tmpList.compactMap({ (str,idx) in
+            return str
+        }).joined(separator: "\n")
+        refreshData()
+    }
+    
+    func siManager(_ manager: JoyGemSIManager, didUpdateTranslate model: JoyGemAsrModel) {
+        let msg = tmpOtherList.firstIndex (where: { (str,idx) in
+            return idx == model.index;
+        })
+        if (msg == nil) {
+            tmpOtherList.append((model.text,model.index))
+        } else {
+            tmpOtherList[msg!] = (model.text,model.index)
+        }
+        
+        guard let model = dataList.last else {
+            return
+        }
+        
+        model.response = tmpOtherList.compactMap({ (str,idx) in
+            return str
+        }).joined(separator: "\n")
+        refreshData()
+    }
+    
+    func siManager(_ manager: JoyGemSIManager, saveFile path: String) {
+        
+    }
+    
+    
+    func siManager(_ manager: JoyGemSIManager, stateChange code: JoyGemStateCode) {
+        
+    }
+    
+    func siManagerStopped(_ manager: JoyGemSIManager) {
+        
+    }
+    func siManager(_ manager: JoyGemSIManager, didUpdateAudioData data: Data, sampleRate: UInt) {
+        PcmSound.sharedInstance().feed(data)
+    }
+    func siManager(_ manager: JoyGemSIManager, onError code: JoyGemErrorCode) {
+        
+    }
+}
+
+extension JoyDialogTranslateVC : JoyGemSIDeviceManagerDelegate {
+
+    
+    
+    func siDeviceManagerStopped(_ manager: JoyGemSIDeviceManager, reason code: JoyGemRecordStopCode) {
+        if let model = dataList.last,model.send.isEmpty{
+            dataList.removeLast()
+            refreshData()
+        }
+    }
+    
+    func siDeviceManager(_ manager: JoyGemSIDeviceManager, didUpdateRaw model: JoyGemAsrModel) {
+        Logger.debug("text: \(model.text),index: \(model.index)")
+        let msg = tmpList.firstIndex (where: { (str,idx) in
+            return idx == model.index;
+        })
+        if (msg == nil) {
+            tmpList.append((model.text,model.index))
+        } else {
+            tmpList[msg!] = (model.text,model.index)
+        }
+        
+        ///监听到一句话结束,自动结束当前录音
+        if (model.definite == true) {
+            toolBar.stopRecord(.earphone)
+            stopEarRecord()
+        }
+        
+        guard let model = dataList.last else {
+            return
+        }
+        
+        model.send = tmpList.compactMap({ (str,idx) in
+            return str
+        }).joined(separator: "\n")
+        refreshData()
+    }
+    
+    func siDeviceManager(_ manager: JoyGemSIDeviceManager, didUpdateTranslate model: JoyGemAsrModel) {
+        let msg = tmpOtherList.firstIndex (where: { (str,idx) in
+            return idx == model.index;
+        })
+        if (msg == nil) {
+            tmpOtherList.append((model.text,model.index))
+        } else {
+            tmpOtherList[msg!] = (model.text,model.index)
+        }
+        
+        guard let model = dataList.last else {
+            return
+        }
+        
+        model.response = tmpOtherList.compactMap({ (str,idx) in
+            return str
+        }).joined(separator: "\n")
+        refreshData()
+    }
+    func siDeviceManager(_ manager: JoyGemSIDeviceManager, didUpdateAudioData data: Data, sampleRate: UInt) {
+        PcmSound.sharedInstance().feed(data)
+    }
+    func siDeviceManager(_ manager: JoyGemSIDeviceManager, update mode: JoyGemRecordMode) {
+        
+    }
+    
+    func siDeviceManager(_ manager: JoyGemSIDeviceManager, saveFile path: String) {
+        Logger.debug("path: \(path)")
+    }
+    
+    func siDeviceManager(_ manager: JoyGemSIDeviceManager, stateChange code: JoyGemStateCode) {
+        Logger.debug("code: \(code)")
+    }
+    
+    func siDeviceManager(_ manager: JoyGemSIDeviceManager, onError code: JoyGemErrorCode) {
+        
+    }
+}
+
+
+extension JoyDialogTranslateVC : PcmSoundDelegate {
+    func remaining(_ frames: UInt) {
+        Logger.debug("[frame:\(frames)]");
+    }
+}
@@ -1,5 +1,5 @@
 //
-//  StereoCallVC.swift
+//  JoyStereoCallVC.swift
 //  ShenYin
 //
 //  Created by liangshi on 2025/11/26.
@@ -7,7 +7,7 @@
 
 import UIKit
 import AVFAudio
-class StereoCallVC:BaseVC{
+class JoyStereoCallVC:BaseVC{
     let toolBar = FieldRecordBar()
     let line = UIView()
     let selfTextView = UITextView()
@@ -196,7 +196,7 @@ class StereoCallVC:BaseVC{
     }
 }
 
-extension StereoCallVC:FieldRecordBarDelegate{
+extension JoyStereoCallVC:FieldRecordBarDelegate{
     func fieldRecordBar(_ bar: FieldRecordBar, languageChange language: CCAiLanguage) {
         JoySdkManager.shared.callRecord.setCallRecoordLanage(language)
         AiLanguageCache.save(type: .thly, lan: language)
diff --git a/ShenYin/ShenYin/Sdk/JoySdk/VC/JoyTctyVC.swift b/ShenYin/ShenYin/Sdk/JoySdk/VC/JoyTctyVC.swift
new file mode 100644 (file)
index 0000000..5931db3
--- /dev/null
@@ -0,0 +1,155 @@
+//
+//  JoyDctyVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/8/6.
+//
+
+import UIKit
+import AVFoundation
+class JoyTctyVC:BaseVC{
+    let toolBar = RecordBar()
+    let textView = PlaceHolderTextView()
+    let translateTextView = PlaceHolderTextView()
+    let sdk = JoyTcty()
+    
+    deinit {
+        CCAiManager.stopPlay()
+    }
+    override func setupViews() {
+        view.addSubviews([toolBar,textView,translateTextView])
+        navBar.title = "同传听译".l10n
+        navBar.rightView.isHidden = false
+        navBar.rightView.setImage(UIImage(named: "help_icon"), for: .normal)
+        
+        toolBar.do { v in
+            v.backgroundColor = .clear
+            v.delegate = self
+            v.recordBtn.activeView.backgroundColor = .hexString("#F7F7F7")
+            v.leftItem.valueLabel.text = "手机".l10n
+            v.leftItem.language = AiLanguageCache.get(type: .tscy_left) ?? .chinse
+            v.rightItem.language = AiLanguageCache.get(type: .tscy_right) ?? .english
+        }
+        view.backgroundColor = .hexString("#F7F7F7")
+        
+        textView.do { v in
+            v.placeHolde = "此处将显示原文".l10n
+            v.textView.textColor = .hexString("#666666")
+        }
+        translateTextView.do { v in
+            v.textView.textColor = .hexString("#121212")
+            v.placeHolde = "此处将显示译文".l10n
+            v.bgView.yc.addgradientLayer(colors: [.hexString("#0092E1"),.hexString("#F7F7F7")], direction: .top2down,rect: CGRect(x: 0, y: 0, width: kScreenWid, height: (kScreenHei - kNavBtm - 120 - 10)/2))
+            v.bgView.alpha = 0.1
+            v.yc.setCorners(withRoundedRect: CGRect(x: 0, y: 0, width: kScreenWid, height: (kScreenHei - kNavBtm - 120 - 10)/2), roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 20, height: 20))
+        }
+    }
+    override func setupLayout() {
+        toolBar.snp.makeConstraints { make in
+            make.width.centerX.equalToSuperview()
+            make.height.equalTo(120)
+            make.bottom.equalToSuperview().inset(20)
+        }
+        textView.snp.makeConstraints { make in
+            make.top.equalToSuperview().inset(80)
+            make.left.right.equalToSuperview()
+            make.height.equalTo((kScreenHei - kNavBtm - 120 - 10)/2)
+        }
+        translateTextView.snp.makeConstraints { make in
+            make.top.equalTo(textView.snp.bottom).offset(10)
+            make.left.right.equalToSuperview()
+            make.bottom.equalTo(toolBar.snp.top)
+        }
+    }
+    override func setupData() {
+        if TipsCache.needShow(type: .tongchuan) {
+            RecordTipsView(type: .tongchuan).show()
+        }
+        
+        sdk.stopCallback = {[weak self] in
+            guard let self = self else{return}
+            toolBar.recordBtn.isSelected = false
+        }
+        sdk.refreshOriginCallback = {[weak self] text in
+            guard let self = self else{return}
+            textView.text = text
+            textView.textView.scrollToBottom()
+        }
+        sdk.refreshTranslateCallback = {[weak self] text in
+            guard let self = self else{return}
+            translateTextView.text = text
+            translateTextView.textView.scrollToBottom()
+        }
+        sdk.filePathCallback = { [weak self] path in
+            guard let self = self else{return}
+            guard let wavPath = DCFolder.recordFileResave(srcPath: path) else{return}
+            let v = PopTextField()
+            v.show()
+            v.cancelTap = {
+                App.shared.popPage()
+            }
+            v.okTap = { [weak self]  text in
+                guard let self = self else{return}
+                
+                let model = RecordDetailModel()
+                model.type = .translate
+                model.from = toolBar.leftItem.language
+                model.to = toolBar.rightItem.language
+                model.name = text
+                model.path = wavPath
+                model.duration = YLToos.getDuration(model.path)
+                model.origin = textView.text ?? ""
+                model.translate = translateTextView.text ?? ""
+                App.shared.popPage(animated: false)
+                App.shared.goToPage(RecordDetailVC(record: model))
+            }
+        }
+    }
+    override func navRightViewTapped() {
+        let tip = RecordTipsView(type: .tongchuan)
+        tip.show()
+    }
+
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = false
+        
+        PcmSound.sharedInstance().delegate = sdk
+        CCAiManager.preparePlayBlueTooth()
+    }
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = true
+        CCAiManager.stopPlay()
+    }
+    override func navleftViewTapped() {
+        if toolBar.recordBtn.isSelected {
+            CustomAlert.showWith(title: "提示".l10n, messgage: "当前正在录音,确定放弃当前录音吗?".l10n, cancelCallback: {}) {
+                self.sdk.stop()
+                CCAiManager.stopPlay()
+                super.navleftViewTapped()
+            }
+        }
+        else{
+            super.navleftViewTapped()
+        }
+    }
+}
+
+extension JoyTctyVC:RecordBarDelegate{
+    func recordBarLanguageChange(_ bar:RecordBar){
+        AiLanguageCache.save(type: .tscy_left, lan: bar.leftItem.language)
+        AiLanguageCache.save(type: .tscy_right, lan: bar.rightItem.language)
+    }
+    func recordBar(_ bar: RecordBar, stateChange state: RecordBarSate) {
+        if state == .start {
+            textView.text = nil
+            translateTextView.text = nil
+            sdk.start(from: toolBar.leftItem.language.value, to: toolBar.rightItem.language.value)
+        }
+        else if state == .stop{
+            sdk.stop()
+            bar.startAnimating()
+        }
+    }
+}
@@ -7,7 +7,7 @@
 
 import UIKit
 import AVFAudio
-class FieldRecordingVC:BaseVC{
+class JoyXclyVC:BaseVC{
     let toolBar = FieldRecordBar()
     let textView = UITextView()
     let rhythView = RhythView()
@@ -68,7 +68,6 @@ class FieldRecordingVC:BaseVC{
      func setupSdk() {
         JoySdkManager.shared.record.recordFinish = {[weak self] path in
             guard let self = self else { return  }
-            Logger.debug("0000000 ===",textView.text ?? "")
             if !needSave {
                 return
             }
@@ -133,7 +132,7 @@ class FieldRecordingVC:BaseVC{
     }
 }
 
-extension FieldRecordingVC:FieldRecordBarDelegate{
+extension JoyXclyVC:FieldRecordBarDelegate{
     func fieldRecordBar(_ bar: FieldRecordBar, languageChange language: CCAiLanguage) {
         JoySdkManager.shared.record.setRecoordLanage(language)
         AiLanguageCache.save(type: .xcly, lan: language)
index a8766c9..3b0eeac 100644 (file)
@@ -5,7 +5,6 @@
 //  Created by liangshi on 2025/9/3.
 //
 import UIKit
-import JoyGemAI
 
 class ChatNavigationBarItem: BaseView {
     let icon = UIImageView()
@@ -44,7 +43,7 @@ class ChatRoleItem: BaseView {
     let icon = UIImageView()
     let titleLabel = UILabel()
     let deslabel = UILabel()
-    var role:JoyGemSpeaker!{
+    var role:CCRole!{
         didSet{
             titleLabel.text = role.name
             icon.image = UIImage(named: "role_" + role.role)
index c791423..38600de 100644 (file)
@@ -6,14 +6,13 @@
 //
 
 import UIKit
-import JoyGemAI
 class RoleListVC: BaseVC{
     let tableView = UITableView(frame: .zero, style: .plain)
-    let list:[JoyGemSpeaker]
-    let selectedModel:JoyGemSpeaker
-    var didSelected:((JoyGemSpeaker)->Void)?
+    let list:[CCRole]
+    let selectedModel:CCRole
+    var didSelected:((CCRole)->Void)?
     
-    init(list: [JoyGemSpeaker], selectedModel: JoyGemSpeaker, didSelected: ((JoyGemSpeaker) -> Void)? = nil) {
+    init(list: [CCRole], selectedModel: CCRole, didSelected: ((CCRole) -> Void)? = nil) {
         self.list = list
         self.selectedModel = selectedModel
         self.didSelected = didSelected
@@ -87,7 +86,7 @@ class RoleListCell: BaseTableViewCell {
     let titleLabel = UILabel()
     let selectedIcon = UIImageView()
     let line = UIView()
-    var data:JoyGemSpeaker!{
+    var data:CCRole!{
         didSet{
             titleLabel.text = data.name
             icon.image = UIImage(named: "role_" + data.role)
diff --git a/ShenYin/ShenYin/UI/Home/RecordTranslateVC.swift b/ShenYin/ShenYin/UI/Home/RecordTranslateVC.swift
deleted file mode 100644 (file)
index 9f0127a..0000000
+++ /dev/null
@@ -1,242 +0,0 @@
-//
-//  RecordTranslateVC.swift
-//  ShenYin
-//
-//  Created by liangshi on 2025/8/6.
-//
-
-import UIKit
-import JoyGemAI
-import AVFoundation
-class RecordTranslateVC:BaseVC{
-    let toolBar = RecordBar()
-    let textView = PlaceHolderTextView()
-    let translateTextView = PlaceHolderTextView()
-    var messageList : [(String,UInt)] = []
-    var translateList : [(String,UInt)] = []
-    var recordFilePath : String?
-    
-    deinit {
-        PcmSound.sharedInstance().stopAudioUnit()
-        PcmSound.sharedInstance().cleanup()
-    }
-    override func setupViews() {
-        view.addSubviews([toolBar,textView,translateTextView])
-        
-        navBar.title = "同传听译".l10n
-        navBar.rightView.isHidden = false
-        navBar.rightView.setImage(UIImage(named: "help_icon"), for: .normal)
-        
-        toolBar.do { v in
-            v.backgroundColor = .clear
-            v.delegate = self
-            v.recordBtn.activeView.backgroundColor = .hexString("#F7F7F7")
-            v.leftItem.valueLabel.text = "手机".l10n
-            v.leftItem.language = AiLanguageCache.get(type: .tscy_left) ?? .chinse
-            v.rightItem.language = AiLanguageCache.get(type: .tscy_right) ?? .english
-        }
-        view.backgroundColor = .hexString("#F7F7F7")
-        
-        textView.do { v in
-            v.placeHolde = "此处将显示原文".l10n
-            v.textView.textColor = .hexString("#666666")
-        }
-        translateTextView.do { v in
-            v.textView.textColor = .hexString("#121212")
-            v.placeHolde = "此处将显示译文".l10n
-            v.bgView.yc.addgradientLayer(colors: [.hexString("#0092E1"),.hexString("#F7F7F7")], direction: .top2down,rect: CGRect(x: 0, y: 0, width: kScreenWid, height: (kScreenHei - kNavBtm - 120 - 10)/2))
-            v.bgView.alpha = 0.1
-            v.yc.setCorners(withRoundedRect: CGRect(x: 0, y: 0, width: kScreenWid, height: (kScreenHei - kNavBtm - 120 - 10)/2), roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 20, height: 20))
-        }
-    }
-    override func setupLayout() {
-        toolBar.snp.makeConstraints { make in
-            make.width.centerX.equalToSuperview()
-            make.height.equalTo(120)
-            make.bottom.equalToSuperview().inset(20)
-        }
-        textView.snp.makeConstraints { make in
-            make.top.equalToSuperview().inset(80)
-            make.left.right.equalToSuperview()
-            make.height.equalTo((kScreenHei - kNavBtm - 120 - 10)/2)
-        }
-        translateTextView.snp.makeConstraints { make in
-            make.top.equalTo(textView.snp.bottom).offset(10)
-            make.left.right.equalToSuperview()
-            make.bottom.equalTo(toolBar.snp.top)
-        }
-    }
-    override func setupData() {
-        JoyGemSIManager.shared().delegate = self
-        
-        PcmSound.sharedInstance().delegate = self;
-        do {
-            try AVAudioSession.sharedInstance().setCategory(.playback)
-            try AVAudioSession.sharedInstance().setActive(true)
-        } catch {
-            Logger.debug(error)
-        }
-        PcmSound.sharedInstance().setup(16000, withchannels: 1)
-        
-        if TipsCache.needShow(type: .tongchuan) {
-            RecordTipsView(type: .tongchuan).show()
-        }
-    }
-    override func navRightViewTapped() {
-        let tip = RecordTipsView(type: .tongchuan)
-        tip.show()
-    }
-
-    override func viewDidAppear(_ animated: Bool) {
-        super.viewDidAppear(animated)
-        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = false
-    }
-    override func viewDidDisappear(_ animated: Bool) {
-        super.viewDidDisappear(animated)
-        navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = true
-        
-        PcmSound.sharedInstance().stopAudioUnit()
-        PcmSound.sharedInstance().cleanup()
-    }
-    override func navleftViewTapped() {
-        if toolBar.recordBtn.isSelected {
-            CustomAlert.showWith(title: "提示".l10n, messgage: "当前正在录音,确定放弃当前录音吗?".l10n, cancelCallback: {}) {
-                JoyGemSIManager.shared().stop()
-                PcmSound.sharedInstance().stopAudioUnit()
-                PcmSound.sharedInstance().cleanup()
-                super.navleftViewTapped()
-            }
-        }
-        else{
-            super.navleftViewTapped()
-        }
-    }
-}
-
-extension RecordTranslateVC:RecordBarDelegate{
-    func recordBarLanguageChange(_ bar:RecordBar){
-        AiLanguageCache.save(type: .tscy_left, lan: bar.leftItem.language)
-        AiLanguageCache.save(type: .tscy_right, lan: bar.rightItem.language)
-    }
-    func recordBar(_ bar: RecordBar, stateChange state: RecordBarSate) {
-        if state == .start {
-            messageList.removeAll()
-            translateList.removeAll()
-            textView.text = nil
-            translateTextView.text = nil
-            
-            if Thread.isMainThread {
-                do {
-                    try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [])
-                    try AVAudioSession.sharedInstance().setActive(true)
-                } catch let error as NSError {
-                    Logger.debug("An error occurred setting the audio session category: \(error.localizedDescription)")
-                }
-            } else {
-                DispatchQueue.main.sync {
-                    do {
-                        try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [])
-                        try AVAudioSession.sharedInstance().setActive(true)
-                    } catch let error as NSError {
-                        Logger.debug("An error occurred setting the audio session category: \(error.localizedDescription)")
-                    }
-                }
-            }
-            JoyGemSIManager.shared().originalLanguage = toolBar.leftItem.language.value
-            JoyGemSIManager.shared().targetLanguage = toolBar.rightItem.language.value
-            JoyGemSIManager.shared().start()
-        }
-        else if state == .stop{
-            JoyGemSIManager.shared().stop()
-            bar.startAnimating()
-        }
-    }
-}
-
-extension RecordTranslateVC : JoyGemSIManagerDelegate {
-    func siManagerStopped(_ manager: JoyGemSIManager, reason code: JoyGemRecordStopCode) {
-        if (code == .actively) {
-//            YLprogressHUD.showAutoHiden("用户主动结束")
-        } else {
-            toolBar.recordBtn.isSelected = false
-        }
-    }
-    
-    func siManager(_ manager: JoyGemSIManager, didUpdateRaw model: JoyGemAsrModel) {
-        let msg = messageList.firstIndex (where: { (str,idx) in
-            return idx == model.index;
-        })
-        if (msg == nil) {
-            messageList.append((model.text,model.index))
-        } else {
-            messageList[msg!] = (model.text,model.index)
-        }
-        
-        textView.text = messageList.compactMap({ (str,idx) in
-            return str
-        }).joined(separator: "\n")
-        textView.textView.scrollToBottom()
-    }
-    
-    func siManager(_ manager: JoyGemSIManager, didUpdateTranslate model: JoyGemAsrModel) {
-        Logger.debug("translate text: \(model.text),index: \(model.index)")
-        let msg = translateList.firstIndex (where: { (str,idx) in
-            return idx == model.index;
-        })
-        if (msg == nil) {
-            translateList.append((model.text,model.index))
-        } else {
-            translateList[msg!] = (model.text,model.index)
-        }
-        
-        translateTextView.text = translateList.compactMap({ (str,idx) in
-            return str
-        }).joined(separator: "\n")
-        translateTextView.textView.scrollToBottom()
-    }
-    
-    
-    func siManager(_ manager: JoyGemSIManager, saveFile path: String) {
-        Logger.debug("path: \(path)")
-        recordFilePath = path
-        guard let path = recordFilePath,let wavPath = DCFolder.recordFileResave(srcPath: path) else{return}
-        let v = PopTextField()
-        v.show()
-        v.cancelTap = {
-            App.shared.popPage()
-        }
-        v.okTap = { [weak self]  text in
-            guard let self = self else{return}
-            
-            let model = RecordDetailModel()
-            model.type = .translate
-            model.from = toolBar.leftItem.language
-            model.to = toolBar.rightItem.language
-            model.name = text
-            model.path = wavPath
-            model.duration = YLToos.getDuration(model.path)
-            model.origin = textView.text ?? ""
-            model.translate = translateTextView.text ?? ""
-            App.shared.popPage(animated: false)
-            App.shared.goToPage(RecordDetailVC(record: model))
-        }
-    }
-    
-    func siManager(_ manager: JoyGemSIManager, stateChange code: JoyGemStateCode) {
-        Logger.debug("code: \(code)")
-    }
-
-    func siManager(_ manager: JoyGemSIManager, didUpdateAudioData data: Data, sampleRate: UInt) {
-        PcmSound.sharedInstance().feed(data)
-    }
-    func siManager(_ manager: JoyGemSIManager, onError code: JoyGemErrorCode) {
-        
-    }
-}
-
-extension RecordTranslateVC : PcmSoundDelegate {
-    
-    func remaining(_ frames: UInt) {
-        Logger.debug("[frame:\(frames)]");
-    }
-}
index 6a0367d..0ded13c 100644 (file)
@@ -185,20 +185,15 @@ class HomeCell: BaseView {
         }
         switch type {
         case .fileRecord:
-            App.shared.goToPage(FieldRecordingVC())
+            CCPage.gotoFieldRecord()
         case .callRecord:
-            if JoySdkManager.shared.callRecord.supportStereo{
-                App.shared.goToPage(StereoCallVC())
-            }
-            else{
-                App.shared.goToPage(CallingRecordVC())
-            }
+            CCPage.gotoCallRecord()
         case .chinese2English:
-            App.shared.goToPage(Chinese2EnglishVC())
+            CCPage.gotoChinese2Enlgish()
         case .recordTranslate:
-            App.shared.goToPage(RecordTranslateVC())
+            CCPage.gotoTcty()
         case .dialogTranslate:
-            App.shared.goToPage(DialogTranslateVC())
+            CCPage.gotoDialogTranslate()
         case .textTranslate:
             App.shared.goToPage(TextTranlateVC())
         }
index fcc0708..95a57fa 100644 (file)
@@ -88,7 +88,7 @@ class HomeHeadView: BaseView {
     }
     @objc func chatAction(){
         if CCBleManager.shared.isConnetd {
-            App.shared.goToPage(ChatVC())
+            CCPage.gotoChat()
         }
     }
     @objc func deviceChange(_ noti:Notification){
index e58eefe..1af0ed4 100644 (file)
@@ -6,7 +6,7 @@
 //
 
 import UIKit
-import JoyGemAI
+
 class TextTranlateVC:BaseVC{
     let scrollView = UIScrollView()
     let languageItem = LanguageSelectItem()
@@ -95,16 +95,14 @@ extension TextTranlateVC:SendToolBarDelegate{
         let from = languageItem.leftLabel.language.value
         let to = languageItem.rightLabel.language.value
         
-        JoyGemAIManager.shared().translate(text, from: from, to: to) {[weak self] result, finish in
+        CCAiManager.translate(text:text, from: from, to: to) { [weak self] response in
             guard let self = self else{return}
+            guard let (result, finish) = response else{return}
             if !finish{
                 dataList.last!.response = dataList.last!.response + result
                 refreshData()
             }
 
-        } error: { error in
-            
         }
-
     }
 }