CODE_SIGN_ENTITLEMENTS = ShenYin/ShenYin.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 101;
+ CURRENT_PROJECT_VERSION = 102;
DEVELOPMENT_TEAM = G23X75CJ67;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
"$(PROJECT_DIR)/ShenYin/UI/Mine/Update/SDKBleManager",
"$(PROJECT_DIR)/ShenYin/SDKBleManager",
+ "$(PROJECT_DIR)/ShenYin/Ota",
);
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShenYin/Info.plist;
CODE_SIGN_ENTITLEMENTS = ShenYin/ShenYin.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 101;
+ CURRENT_PROJECT_VERSION = 102;
DEVELOPMENT_TEAM = G23X75CJ67;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
"$(PROJECT_DIR)/ShenYin/UI/Mine/Update/SDKBleManager",
"$(PROJECT_DIR)/ShenYin/SDKBleManager",
+ "$(PROJECT_DIR)/ShenYin/Ota",
);
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShenYin/Info.plist;
task.downloadHandler = { response, tempUrl in
DispatchQueue.main.sync {
let name = Date().format(.full5)
- if let destinationUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("\(name).\(type)") {
+ if let destinationUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("\(name).\(type)") {
if FileManager.default.fileExists(atPath: destinationUrl.path) {
Logger.debug("文件已经存在,先移除 => ",url)
do {
}
task.downloadHandler = { response, tempUrl in
DispatchQueue.main.sync {
- if let destinationUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("\(Date().timeIntervalSince1970).pdf") {
+ if let destinationUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("\(Date().timeIntervalSince1970).pdf") {
Logger.debug("保存路径 => ",destinationUrl)
if FileManager.default.fileExists(atPath: destinationUrl.path) {
Logger.debug("文件已经存在,先移除 => ",url)
--- /dev/null
+//
+// JoyCallRecord.swift
+// ShenYin
+//
+// Created by liangshi on 2025/12/4.
+//
+
+import UIKit
+import JoyGemAI
+
+class JoyCallRecord:NSObject{
+ var recordList : [(String,UInt)] = []
+ var recordFinish:((String)->Void)?
+ var recordNeedReloadData:((String)->Void)?
+ var recordText = ""
+
+ /// 立体声
+ var selfList : [(String,UInt)] = []
+ var otherList : [(String,UInt)] = []
+ var selfText = ""
+ var otherText = ""
+
+ var stereoNeedReloadData:((String,UInt)->Void)?
+ //通话录音回调,未在通话
+ var callRecordNotOnCall:(()->Void)?
+ //通话录音回调,自动结束
+ var callRecordAutoFinish:(()->Void)?
+
+ //知否支持立体声
+ var supportStereo:Bool{
+ return JoyGemCallManager.shared().supportStereo == true
+ }
+
+
+ override init() {
+ super.init()
+ //通话录音
+ JoyGemCallManager.shared().delegate = self
+ //通话录音、立体声
+ JoyGemCallManager.shared().stereoDelegate = self
+ }
+
+
+ func setCallRecoordLanage(_ language:AiSupportLanguage){
+ JoyGemCallManager.shared().originalLanguage = language.value
+ }
+ func callRecordStart(){
+ recordList.removeAll()
+ selfList.removeAll()
+ otherList.removeAll()
+ recordText = ""
+ selfText = ""
+ otherText = ""
+ JoyGemCallManager.shared().start()
+ }
+ func callRecordStop(){
+ JoyGemCallManager.shared().stop()
+ }
+}
+
+extension JoyCallRecord : JoyGemCallManagerDelegate {
+
+ func callManagerStopped(_ manager: JoyGemCallManager, reason code: JoyGemRecordStopCode) {
+ if (code == .actively) {
+ Logger.debug( "用户主动结束")
+ } else {
+ Logger.debug( "自动结束")
+ callRecordAutoFinish?()
+ }
+ }
+
+ func callManagerNotonCall(_ manager: JoyGemCallManager) {
+ callRecordNotOnCall?()
+ }
+
+ func callManager(_ manager: JoyGemCallManager, saveFile path: String) {
+ Logger.debug("path: \(path)")
+ if path.isEmpty {
+ return
+ }
+ guard let wavPath = DCFolder.recordFileResave(srcPath: path) else{return}
+ recordFinish?(wavPath)
+ }
+
+ func callManager(_ manager: JoyGemCallManager, stateChange code: JoyGemStateCode) {
+ Logger.debug("code: \(code)")
+ }
+
+ func callManager(_ manager: JoyGemCallManager, onError code: JoyGemErrorCode) {
+
+ }
+
+ func callManager(_ manager: JoyGemCallManager, didUpdateAsr model: JoyGemAsrModel) {
+ Logger.debug("text: \(model.text),index: \(model.index)")
+ let msg = recordList.firstIndex (where: { (str,idx) in
+ return idx == model.index;
+ })
+ if (msg == nil) {
+ recordList.append((model.text,model.index))
+ } else {
+ recordList[msg!] = (model.text,model.index)
+ }
+ recordText = recordList.compactMap({ (str,idx) in
+ return str
+ }).joined(separator: "\n")
+ recordNeedReloadData?(recordText)
+ }
+}
+
+extension JoyCallRecord:JoyGemCallStereoDelegate{
+ func callManager(_ manager: JoyGemCallManager, didUpdateStereoAsr model: JoyGemAsrModel, channel index: UInt) {
+ debugPrint("text: \(model.text),index: \(model.index)")
+ if (index == 1) {
+ let msg = self.selfList.firstIndex (where: { (str,idx) in
+ return idx == model.index;
+ })
+ if (msg == nil) {
+ selfList.append((model.text,model.index))
+ } else {
+ selfList[msg!] = (model.text,model.index)
+ }
+ selfText = selfList.compactMap({ (str,idx) in
+ return str
+ }).joined(separator: "\n")
+ stereoNeedReloadData?(selfText,index)
+ } else if (index == 2) {
+ let msg = otherList.firstIndex (where: { (str,idx) in
+ return idx == model.index;
+ })
+ if (msg == nil) {
+ otherList.append((model.text,model.index))
+ } else {
+ otherList[msg!] = (model.text,model.index)
+ }
+ otherText = otherList.compactMap({ (str,idx) in
+ return str
+ }).joined(separator: "\n")
+ stereoNeedReloadData?(otherText,index)
+ }
+ }
+
+ func callManager(_ manager: JoyGemCallManager, stereoIntensity lists: [[NSNumber]], timestamp: TimeInterval) {
+ // selfIntensitiesView.intensities = lists[0].map{ num in
+ // num.doubleValue
+ // }
+ // otherIntensitiesView.intensities = lists[1].map{ num in
+ // num.doubleValue
+ // }
+ }
+
+}
//
-// JoySdkManager.swift
+// JoyChat.swift
// ShenYin
//
-// Created by liangshi on 2025/8/29.
+// Created by liangshi on 2025/12/4.
//
+
+import AVFoundation
import JoyGemAI
-import UIKit
import SVProgressHUD
-import AVFoundation
+import UIKit
-class JoySdkManager:NSObject{
- static let shared = JoySdkManager()
-
+import UIKit
+import JoyGemAI
+
+class JoyChat:NSObject{
var isTtsPlaying = false
var dataList:[ChatCellModel] = []
var chatStateChange:((JoyGemStateCode)->Void)?
var chatNeedReloadData:(()->Void)?
- var supportStereo:Bool{
- return JoyGemCallManager.shared().supportStereo == true
+ override init() {
+ super.init()
+ // AI对话
+ JoyGemChatManager.shared().delegate = self
}
-}
-extension JoySdkManager {
- func setupSdk(){
- //MARK: 初始化SDK
- JoyGemAIManager.shared().initSdk()
- //MARK: 配置日志类型
- // JoyGemAIManager.shared().setLogMode(JoyGemLogMode.all)
- JoyGemAIManager.shared().setLogMode(JoyGemLogMode.close)
-
- let fileManager = FileManager.default
- if let appSupportDirectory = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first {
- let logDirectory = appSupportDirectory.appendingPathComponent("log")
- // 检查 log 文件夹是否存在
- if !fileManager.fileExists(atPath: logDirectory.path) {
- do {
- // 创建 log 文件夹
- try fileManager.createDirectory(at: logDirectory, withIntermediateDirectories: true, attributes: nil)
- Logger.debug("log 文件夹已创建: \(logDirectory.path)")
- } catch {
- Logger.debug("创建 log 文件夹时出错: \(error.localizedDescription)")
- }
- } else {
- Logger.debug("log 文件夹已存在: \(logDirectory.path)")
- }
-
- // 获取当前日期并格式化
- let currentDate = Date()
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyyMMddHHmmss"
- let dateString = dateFormatter.string(from: currentDate)
-
- // 创建日志文件名和路径
- let fileName = "SDK_\(dateString).log"
- let filePath = logDirectory.appendingPathComponent(fileName)
-
- // 创建空文件
- if !fileManager.fileExists(atPath: filePath.path) {
- // 创建空文件
- fileManager.createFile(atPath: filePath.path, contents: nil, attributes: nil)
- Logger.debug("日志文件已创建: \(filePath.path)")
- } else {
- Logger.debug("日志文件已存在: \(filePath.path)")
- }
-
- //MARK: 配置日志路径
- JoyGemAIManager.shared().setLogPath(filePath.path)
- } else {
- Logger.debug("无法找到应用支持目录")
- }
- //MARK: 配置缓存路径
- let tempPath = NSTemporaryDirectory();
- JoyGemAIManager.shared().setCacheDirectory(tempPath)
- JoyGemAIManager.shared().setTWSAutoSync(true)
-
- JoyGemChatManager.shared().delegate = self
+ func loadChatCache(){
+ dataList = ChatCache.shared.get()
}
}
+extension JoyChat : JoyGemChatManagerDelegate {
-extension JoySdkManager : JoyGemChatManagerDelegate {
- func loadChatCache(){
- dataList = ChatCache.shared.get()
- }
-
func chatManager(_ manager: JoyGemChatManager, audioData data: Data?, index: UInt, sampleRate: UInt, finish isFinish: Bool) {
PcmSound.sharedInstance().delegate = self
if (!isTtsPlaying) {
PcmSound.sharedInstance().setup(16000, withchannels: 1)
var options: AVAudioSession.CategoryOptions = [.allowBluetooth]
-// if App.shared.isInBackground{
-// options = [.mixWithOthers]
-// }
+ // if App.shared.isInBackground{
+ // options = [.mixWithOthers]
+ // }
do {
return
}
if text.isEmpty {
-// if dataList.last?.id == -1 {
-// dataList.removeLast()
-// chatNeedReloadData?()
-// }
+ // if dataList.last?.id == -1 {
+ // dataList.removeLast()
+ // chatNeedReloadData?()
+ // }
return
}
if dataList.last?.id == -1 {
}
}
}
-extension JoySdkManager : PcmSoundDelegate {
-
+
+extension JoyChat : PcmSoundDelegate {
func remaining(_ frames: UInt) {
Logger.debug("[frame:\(frames)]");
if (frames == 0) {
--- /dev/null
+//
+// Joy.swift
+// ShenYin
+//
+// Created by liangshi on 2025/12/4.
+//
+import UIKit
+import JoyGemAI
+
+class JoyRecord:NSObject{
+ var recordFinish:((String)->Void)?
+ var recordNeedReloadData:((String)->Void)?
+ var recordList : [(String,UInt)] = []
+ var recordText = ""
+
+ override init() {
+ super.init()
+ //现场录音
+ JoyGemRecordManager.shared().delegate = self
+ }
+
+ func setRecoordLanage(_ language:AiSupportLanguage){
+ JoyGemRecordManager.shared().originalLanguage = language.value
+ }
+ func recordStart(){
+ Logger.debug("开启录音")
+ recordList.removeAll()
+ recordText = ""
+ JoyGemRecordManager.shared().start()
+ }
+ func recordResume(){
+ JoyGemRecordManager.shared().resume()
+ }
+ func recordPause(){
+ JoyGemRecordManager.shared().pause()
+ }
+ func recordStop(){
+ JoyGemRecordManager.shared().stop()
+ }
+}
+
+extension JoyRecord : JoyGemRecordManagerDelegate {
+ func recordManager(_ manager: JoyGemRecordManager, didUpdateAudioData data: Data) {
+ }
+
+ func recordManager(_ manager: JoyGemRecordManager, didUpdateTranslate model: JoyGemAsrModel) {
+
+ }
+
+ func recordManager(_ manager: JoyGemRecordManager, didUpdateAsr model: JoyGemAsrModel) {
+ Logger.debug("text: \(model.text),index: \(model.index)")
+ let msg = recordList.firstIndex (where: { (str,idx) in
+ return idx == model.index;
+ })
+ if (msg == nil) {
+ recordList.append((model.text,model.index))
+ } else {
+ recordList[msg!] = (model.text,model.index)
+ }
+ recordText = recordList.compactMap({ (str,idx) in
+ return str
+ }).joined(separator: "\n")
+ recordNeedReloadData?(recordText)
+ }
+
+
+ func recordManager(_ manager: JoyGemRecordManager, update mode: JoyGemRecordMode) {
+ Logger.debug("update mode: \(mode)")
+ }
+
+ func recordManager(_ manager: JoyGemRecordManager, saveFile path: String) {
+ recordList.removeAll()
+ Logger.debug("path: \(path)")
+ if path.isEmpty {
+ return
+ }
+ guard let wavPath = DCFolder.recordFileResave(srcPath: path) else{return}
+ recordFinish?(wavPath)
+
+ }
+
+ func recordManager(_ manager: JoyGemRecordManager, stateChange code: JoyGemStateCode) {
+ Logger.debug("stateChange: \(code)")
+ }
+
+
+ func recordManagerStopped(_ manager: JoyGemRecordManager, reason code: JoyGemRecordStopCode) {
+ Logger.debug("stop reason: \(code)")
+ if (code == .actively) {
+
+ } else {
+ // YLprogressHUD.showAutoHiden("自动结束")
+ //判断当前蓝牙是否连接从而确定是否因为蓝牙断开造成的录音断开
+ }
+ }
+
+ func recordManager(_ manager: JoyGemRecordManager, onError code: JoyGemErrorCode) {
+ Logger.debug("onError: \(code)")
+ }
+}
--- /dev/null
+//
+// JoySdkManager.swift
+// ShenYin
+//
+// Created by liangshi on 2025/8/29.
+//
+
+import UIKit
+import JoyGemAI
+
+class JoySdkManager:NSObject{
+ static let shared = JoySdkManager()
+ var record:JoyRecord!
+ var callRecord:JoyCallRecord!
+ var chat:JoyChat!
+}
+extension JoySdkManager {
+ func setupSdk(){
+ //MARK: 初始化SDK
+ JoyGemAIManager.shared().initSdk()
+ //MARK: 配置日志类型
+ //JoyGemAIManager.shared().setLogMode(JoyGemLogMode.all)
+ JoyGemAIManager.shared().setLogMode(JoyGemLogMode.close)
+
+ let fileManager = FileManager.default
+ if let appSupportDirectory = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first {
+ let logDirectory = appSupportDirectory.appendingPathComponent("log")
+ // 检查 log 文件夹是否存在
+ if !fileManager.fileExists(atPath: logDirectory.path) {
+ do {
+ // 创建 log 文件夹
+ try fileManager.createDirectory(at: logDirectory, withIntermediateDirectories: true, attributes: nil)
+ Logger.debug("log 文件夹已创建: \(logDirectory.path)")
+ } catch {
+ Logger.debug("创建 log 文件夹时出错: \(error.localizedDescription)")
+ }
+ } else {
+ Logger.debug("log 文件夹已存在: \(logDirectory.path)")
+ }
+
+ // 获取当前日期并格式化
+ let currentDate = Date()
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "yyyyMMddHHmmss"
+ let dateString = dateFormatter.string(from: currentDate)
+
+ // 创建日志文件名和路径
+ let fileName = "SDK_\(dateString).log"
+ let filePath = logDirectory.appendingPathComponent(fileName)
+
+ // 创建空文件
+ if !fileManager.fileExists(atPath: filePath.path) {
+ // 创建空文件
+ fileManager.createFile(atPath: filePath.path, contents: nil, attributes: nil)
+ Logger.debug("日志文件已创建: \(filePath.path)")
+ } else {
+ Logger.debug("日志文件已存在: \(filePath.path)")
+ }
+
+ //MARK: 配置日志路径
+ JoyGemAIManager.shared().setLogPath(filePath.path)
+ } else {
+ Logger.debug("无法找到应用支持目录")
+ }
+
+ //MARK: 配置缓存路径
+ let tempPath = NSTemporaryDirectory();
+ JoyGemAIManager.shared().setCacheDirectory(tempPath)
+ JoyGemAIManager.shared().setTWSAutoSync(true)
+
+ record = JoyRecord()
+ callRecord = JoyCallRecord()
+ chat = JoyChat()
+
+
+
+ let options: AVAudioSession.CategoryOptions = [.allowBluetooth]
+ do {
+ try AVAudioSession.sharedInstance().setCategory(.playback, options: [options])
+ try AVAudioSession.sharedInstance().setActive(true)
+ } catch {
+ Logger.debug(error)
+ }
+ }
+}
+
+
Logger.debug(DCFolder.user)
Logger.debug(DCFolder.record)
window?.rootViewController = BaseTC()
- JoySdkManager.shared.loadChatCache()
+ JoySdkManager.shared.chat.loadChatCache()
}
func goToLogin(){
window?.rootViewController = BaseNC(rootViewController: LoginVC())
if let topVC = App.shared.topVC(), let vc = topVC as? StereoCallVC {
vc.stopRecord()
}
+ JoySdkManager.shared.record.recordStop()
case .recordLive:
Logger.debug("请求开启现场录音")
- if App.shared.isInBackground {
- return
- }
if let vc = App.shared.topVC() as? FieldRecordingVC {
vc.startRecord()
}
else{
App.shared.goToPage(FieldRecordingVC(autoStart: true))
+ JoySdkManager.shared.record.recordStart()
}
case .recordCall:
Logger.debug("请求开启通话录音")
- if App.shared.isInBackground {
- return
- }
- if JoySdkManager.shared.supportStereo{
+ if JoySdkManager.shared.callRecord.supportStereo{
if let vc = App.shared.topVC() as? StereoCallVC {
vc.startRecord()
}else{
App.shared.goToPage(CallingRecordVC(autoStart: true))
+ JoySdkManager.shared.callRecord.callRecordStart()
}
}
else{
vc.startRecord()
}else{
App.shared.goToPage(CallingRecordVC(autoStart: true))
+ JoySdkManager.shared.callRecord.callRecordStart()
}
}
case .si:
Logger.debug( "请求开启同传听译")
case .chat:
- Logger.debug("通知开启了AI对话")
-// YLprogressHUD.showAutoHiden( "通知开启了AI对话")
+ Logger.debug("通知开启AI对话")
if !App.shared.topVC().isKind(of: ChatVC.self) {
App.shared.goToPage(ChatVC())
}
//
import UIKit
-import JoyGemAI
import AVFAudio
class CallingRecordVC:BaseVC{
let toolBar = FieldRecordBar()
let textView = UITextView()
let rhythView = RhythView()
- var messageList : [(String,UInt)] = []
- var recordFilePath : String?
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
}
}
override func setupData() {
- JoyGemCallManager.shared().delegate = self
+ if bgEnter {
+ textView.text = JoySdkManager.shared.callRecord.recordText
+ }
+ }
+ 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}
+
+ 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))
+ }
+ }
+ JoySdkManager.shared.callRecord.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)
+ }
+ 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()
}
if toolBar.recordBtn.isSelected {
CustomAlert.showWith(title: "提示".l10n, messgage: "当前正在录音,确定放弃当前录音吗?".l10n, cancelCallback: {}) {[unowned self] in
self.rhythView.stopTimer()
- JoyGemCallManager.shared().stop()
- PcmSound.sharedInstance().stopAudioUnit()
- PcmSound.sharedInstance().cleanup()
+ self.needSave = false
+ JoySdkManager.shared.callRecord.callRecordStop()
self.navigationController?.popViewController(animated: true)
}
}
super.navleftViewTapped()
}
}
- func refreshData(){
- let range = NSMakeRange(textView.text.count - 1, 1)
- textView.scrollRangeToVisible(range)
- }
- ///播放最后一次录音文件
- @objc func playRecord() {
- guard let file = recordFilePath else {return;}
-
-// do {
-// // 创建 AVAudioPlayer 实例
-// audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: file))
-// audioPlayer?.prepareToPlay() // 准备播放
-// audioPlayer?.play() // 播放音频
-// } catch {
-// debugPrint("播放音频时发生错误: \(error.localizedDescription)")
-// }
- }
func startRecord(){
toolBar.recordBtnAction()
}
extension CallingRecordVC:FieldRecordBarDelegate{
func fieldRecordBar(_ bar: FieldRecordBar, languageChange language: AiSupportLanguage) {
- JoyGemCallManager.shared().originalLanguage = language.value
+ JoySdkManager.shared.callRecord.setCallRecoordLanage(language)
AiLanguageCache.save(type: .thly, lan: language)
}
func fieldRecordBar(_ bar: FieldRecordBar, stateChange state: RecordSate) {
if state == .start {
textView.text = ""
- messageList.removeAll()
rhythView.startTimer()
- JoyGemCallManager.shared().start()
+ JoySdkManager.shared.callRecord.callRecordStart()
}
else if state == .stop{
stopAction()
}
func stopAction(){
rhythView.stopTimer()
- JoyGemCallManager.shared().stop()
+ JoySdkManager.shared.callRecord.callRecordStop()
toolBar.startAnimating()
}
}
-extension CallingRecordVC : JoyGemCallManagerDelegate {
-
- func callManagerStopped(_ manager: JoyGemCallManager, reason code: JoyGemRecordStopCode) {
- if (code == .actively) {
- Logger.debug( "用户主动结束")
- } else {
- Logger.debug( "自动结束")
- stopAction()
- }
- }
-
- func callManagerNotonCall(_ manager: JoyGemCallManager) {
- YLprogressHUD.showAutoHiden("未处于通话状态".l10n)
- rhythView.reset()
- toolBar.recordBtn.isSelected = false
- toolBar.leftItem.alpha = 1
- }
-
- func callManager(_ manager: JoyGemCallManager, 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 = .phone
- model.from = toolBar.leftItem.language
- model.to = .english
- model.name = text
- model.path = wavPath
- model.duration = YLToos.getDuration(model.path)
- model.origin = textView.text
- model.translate = ""
- App.shared.popPage(animated: false)
- App.shared.goToPage(RecordDetailVC(record: model))
- }
- }
-
- func callManager(_ manager: JoyGemCallManager, stateChange code: JoyGemStateCode) {
- Logger.debug("code: \(code)")
- }
-
- func callManager(_ manager: JoyGemCallManager, onError code: JoyGemErrorCode) {
-
- }
-
- func callManager(_ manager: JoyGemCallManager, didUpdateAsr model: JoyGemAsrModel) {
- Logger.debug("text: \(model.text),index: \(model.index)")
- 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")
- refreshData()
- }
-
-}
-
-extension CallingRecordVC:JoyGemCallStereoDelegate{
- func callManager(_ manager: JoyGemCallManager, didUpdateStereoAsr model: JoyGemAsrModel, channel index: UInt) {
-
- }
-}
}
override func setupData() {
JoyGemChatManager.shared().addObserver(self, forKeyPath: "isActive",options: [.new, .old], context: nil);
- JoySdkManager.shared.chatStateChange = {[weak self] code in
+ JoySdkManager.shared.chat.chatStateChange = {[weak self] code in
guard let self = self else { return }
switch code {
case .startRecord:
break
}
}
- JoySdkManager.shared.chatNeedReloadData = {[weak self] in
+ JoySdkManager.shared.chat.chatNeedReloadData = {[weak self] in
guard let self = self else { return }
refreshData()
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
- ChatCache.shared.saveList(JoySdkManager.shared.dataList)
+ ChatCache.shared.saveList(JoySdkManager.shared.chat.dataList)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if (keyPath == "isActive") {
}
func refreshData(){
tableView.reloadData()
- if JoySdkManager.shared.dataList.count > 0 {
- tableView.scrollToRow(at: IndexPath(row: JoySdkManager.shared.dataList.count - 1, section: 0), at: .none, animated: true)
+ 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(){
func recordBarCancel(_ toolBar: ChatRecordBar) {
// JoyGemChatManager.shared().stopChat()
JoyGemChatManager.shared().stopSingleChat()
- if let item = JoySdkManager.shared.dataList.last,item.content.isEmpty{
- JoySdkManager.shared.dataList.removeLast()
+ if let item = JoySdkManager.shared.chat.dataList.last,item.content.isEmpty{
+ JoySdkManager.shared.chat.dataList.removeLast()
refreshData()
}
}
extension ChatVC:UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return JoySdkManager.shared.dataList.count
+ 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.dataList[indexPath.row]
+ cell.data = JoySdkManager.shared.chat.dataList[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
case normal
case spolit
}
-protocol DialogRecordBarDelegate {
+protocol DialogRecordBarDelegate:NSObject {
func recordBar(_ bar:DialogRecordBar,layoutChange :DialogRecordLayoutType)
func recordBar(_ bar:DialogRecordBar, device:DialogRecordDeviceType,stateChange state:RecordBarSate)
}
class DialogRecordBar: BaseView {
let recordView = DialogRecordView()
let languageView = DialogLanguageView()
- var delegate:DialogRecordBarDelegate?
+ weak var delegate:DialogRecordBarDelegate?
var navBar:BaseNavigationBar = BaseNavigationBar()
override func setupViews() {
case pause
}
-protocol FieldRecordBarDelegate {
+protocol FieldRecordBarDelegate:NSObject {
func fieldRecordBar(_ bar:FieldRecordBar,languageChange language:AiSupportLanguage)
func fieldRecordBar(_ bar:FieldRecordBar,stateChange state:RecordSate)
}
let leftItem = RecordBarItem()
let rightItem = RecordBarButton()
var state:RecordSate = .stop
- var delegate:FieldRecordBarDelegate?
+ weak var delegate:FieldRecordBarDelegate?
var hasRightItem:Bool = true
override func setupViews() {
//
import UIKit
-import JoyGemAI
import AVFAudio
class FieldRecordingVC:BaseVC{
let toolBar = FieldRecordBar()
let textView = UITextView()
let rhythView = RhythView()
- var messageList : [(String,UInt)] = []
- var recordFilePath : String?
+
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() {
make.bottom.equalTo(toolBar.snp.top)
}
}
+
override func setupData() {
- JoyGemRecordManager.shared().delegate = self
+ if bgEnter {
+ textView.text = JoySdkManager.shared.record.recordText
+ }
+ }
+
+ func setupSdk() {
+ JoySdkManager.shared.record.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 = .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))
+ }
+ }
+ JoySdkManager.shared.record.recordNeedReloadData = {[weak self] text in
+ guard let self = self else { return }
+ textView.text = text
+ textView.scrollToBottom()
+ }
+
let options: AVAudioSession.CategoryOptions = [.allowBluetooth]
do {
try AVAudioSession.sharedInstance().setCategory(.playback, options: [options])
startRecord()
}
}
-
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = false
override func navleftViewTapped() {
if toolBar.recordBtn.isSelected {
CustomAlert.showWith(title: "提示", messgage: "当前正在录音,确定放弃当前录音吗?".l10n, cancelCallback: {}) {[unowned self] in
+ self.needSave = false
self.rhythView.stopTimer()
- JoyGemRecordManager.shared().stop()
+ JoySdkManager.shared.record.recordStop()
self.navigationController?.popViewController(animated: true)
}
}
super.navleftViewTapped()
}
}
- func scrollToBottom(){
- textView.scrollRangeToVisible(NSMakeRange(textView.text.count - 1, 1))
-
- }
func startRecord(){
toolBar.recordBtnAction()
+ rhythView.startTimer()
}
func stopRecord(){
toolBar.recordBtnAction()
+ rhythView.stopTimer()
}
}
extension FieldRecordingVC:FieldRecordBarDelegate{
func fieldRecordBar(_ bar: FieldRecordBar, languageChange language: AiSupportLanguage) {
- JoyGemRecordManager.shared().originalLanguage = language.value
+ JoySdkManager.shared.record.setRecoordLanage(language)
AiLanguageCache.save(type: .xcly, lan: language)
}
func fieldRecordBar(_ bar: FieldRecordBar, stateChange state: RecordSate) {
if state == .start {
- textView.text = ""
- messageList.removeAll()
- JoyGemRecordManager.shared().start()
+ JoySdkManager.shared.record.recordStart()
rhythView.startTimer()
}
if state == .resume{
rhythView.startTimer()
- JoyGemRecordManager.shared().resume()
+ JoySdkManager.shared.record.recordResume()
}
else if state == .pause{
- JoyGemRecordManager.shared().pause()
+ JoySdkManager.shared.record.recordPause()
rhythView.stopTimer()
}
else if state == .stop{
rhythView.stopTimer()
- JoyGemRecordManager.shared().stop()
+ JoySdkManager.shared.record.recordStop()
bar.startAnimating()
}
}
}
-
-extension FieldRecordingVC : JoyGemRecordManagerDelegate {
-
- func recordManager(_ manager: JoyGemRecordManager, didUpdateAudioData data: Data) {
-// PcmSound.sharedInstance().feed(data)
- }
-
- func recordManager(_ manager: JoyGemRecordManager, didUpdateTranslate model: JoyGemAsrModel) {
-
- }
-
- func recordManager(_ manager: JoyGemRecordManager, didUpdateAsr model: JoyGemAsrModel) {
- Logger.debug("text: \(model.text),index: \(model.index)")
- 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")
-
- let range = NSMakeRange(textView.text.count - 1, 1)
- textView.scrollRangeToVisible(range)
- }
-
-
- func recordManager(_ manager: JoyGemRecordManager, update mode: JoyGemRecordMode) {
- Logger.debug("update mode: \(mode)")
- }
-
- func recordManager(_ manager: JoyGemRecordManager, 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 = .present
- model.from = toolBar.leftItem.language
- model.to = .english
- model.name = text
- model.path = wavPath
- model.duration = YLToos.getDuration(model.path)
- model.origin = textView.text
- model.translate = ""
- App.shared.popPage(animated: false)
- App.shared.goToPage(RecordDetailVC(record: model))
- }
- }
-
- func recordManager(_ manager: JoyGemRecordManager, stateChange code: JoyGemStateCode) {
- Logger.debug("stateChange: \(code)")
- }
-
-
- func recordManagerStopped(_ manager: JoyGemRecordManager, reason code: JoyGemRecordStopCode) {
- Logger.debug("stop reason: \(code)")
- if (code == .actively) {
-// YLprogressHUD.showAutoHiden("用户主动结束")
-// toolBar.recordBtn.stopLoading()
- } else {
-// YLprogressHUD.showAutoHiden("自动结束")
- ///判断当前蓝牙是否连接从而确定是否因为蓝牙断开造成的录音断开
- }
- }
-
- func recordManager(_ manager: JoyGemRecordManager, onError code: JoyGemErrorCode) {
- Logger.debug("onError: \(code)")
- }
-}
case stop
}
-protocol RecordBarDelegate {
+protocol RecordBarDelegate:NSObject {
func recordBarLanguageChange(_ bar:RecordBar)
func recordBar(_ bar:RecordBar,stateChange state:RecordBarSate)
}
let recordBtn = LoadingButton()
let leftItem = RecordBarItem()
let rightItem = RecordBarItem()
- var delegate:RecordBarDelegate?
+ weak var delegate:RecordBarDelegate?
var isChienseEnglish = false{
didSet{
if isChienseEnglish {
// Created by liangshi on 2025/9/5.
//
-protocol RecordDetailBarDelegate {
+protocol RecordDetailBarDelegate:NSObject {
func recordDetailBarTranstlateAction(_ bar:RecordDetailBar)
func recordDetailBarRenameAction(_ bar:RecordDetailBar)
func recordDetailBarPlayAction(_ bar:RecordDetailBar)
let playItem = RecordDetailBarButton()
let moreItem = RecordDetailBarButton()
- var delegate:RecordDetailBarDelegate?
+ weak var delegate:RecordDetailBarDelegate?
override func setupViews() {
backgroundColor = .white
// Created by liangshi on 2025/9/5.
//
-protocol RecordDetailTopBarDelegate {
+@objc protocol RecordDetailTopBarDelegate {
func recordDetailBarTranstlateAction(_ bar:RecordDetailTopBar)
func recordDetailTopBarOgrinAction(_ bar:RecordDetailTopBar)
func recordDetailTopBarSummaryAction(_ bar:RecordDetailTopBar)
let originItem = LanguageItem()
let summaryItem = LanguageItem()
- var delegate:RecordDetailTopBarDelegate?
+ weak var delegate:RecordDetailTopBarDelegate?
override func setupViews() {
backgroundColor = .clear
case .fileRecord:
App.shared.goToPage(FieldRecordingVC())
case .callRecord:
- if JoySdkManager.shared.supportStereo{
+ if JoySdkManager.shared.callRecord.supportStereo{
App.shared.goToPage(StereoCallVC())
}
else{
//
import UIKit
-import JoyGemAI
import AVFAudio
class StereoCallVC:BaseVC{
let toolBar = FieldRecordBar()
private let selfLabel = DotLabel()
private let otherLabel = DotLabel()
let rhythView = RhythView()
- var selfMessageList : [(String,UInt)] = []
- var otherMessageList : [(String,UInt)] = []
- var recordFilePath : String?
+
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 setupData() {
- JoyGemCallManager.shared().delegate = self
- JoyGemCallManager.shared().stereoDelegate = self
+ 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
if toolBar.recordBtn.isSelected {
CustomAlert.showWith(title: "提示".l10n, messgage: "当前正在录音,确定放弃当前录音吗?".l10n, cancelCallback: {}) {[unowned self] in
self.rhythView.stopTimer()
- JoyGemCallManager.shared().stop()
- PcmSound.sharedInstance().stopAudioUnit()
- PcmSound.sharedInstance().cleanup()
+ self.needSave = false
+ JoySdkManager.shared.callRecord.callRecordStop()
self.navigationController?.popViewController(animated: true)
}
}
extension StereoCallVC:FieldRecordBarDelegate{
func fieldRecordBar(_ bar: FieldRecordBar, languageChange language: AiSupportLanguage) {
- JoyGemCallManager.shared().originalLanguage = language.value
+ 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()
+// selfMessageList.removeAll()
+// otherMessageList.removeAll()
selfTextView.text = nil
otherTextView.text = nil
- JoyGemCallManager.shared().start()
+ JoySdkManager.shared.callRecord.callRecordStart()
rhythView.startTimer()
}
else if state == .stop{
}
func stopAction(){
rhythView.stopTimer()
- JoyGemCallManager.shared().stop()
+ JoySdkManager.shared.callRecord.callRecordStop()
toolBar.startAnimating()
}
func gotoDetail( fileName:String,wavPath:String,language:AiSupportLanguage){
App.shared.goToPage(RecordDetailVC(record: model))
}
}
-extension StereoCallVC : JoyGemCallManagerDelegate {
-
- func callManagerStopped(_ manager: JoyGemCallManager, reason code: JoyGemRecordStopCode) {
- if (code == .actively) {
- Logger.debug( "用户主动结束")
- } else {
- Logger.debug( "自动结束")
- stopAction()
- }
- }
-
- func callManagerNotonCall(_ manager: JoyGemCallManager) {
- YLprogressHUD.showAutoHiden("未处于通话状态".l10n)
- rhythView.reset()
- toolBar.recordBtn.isSelected = false
- toolBar.leftItem.alpha = 1
- }
-
- func callManager(_ manager: JoyGemCallManager, 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}
- gotoDetail(fileName: text, wavPath: wavPath, language: toolBar.leftItem.language)
- }
- }
-
- func callManager(_ manager: JoyGemCallManager, stateChange code: JoyGemStateCode) {
- Logger.debug("code: \(code)")
- }
-
- func callManager(_ manager: JoyGemCallManager, onError code: JoyGemErrorCode) {
-
- }
-}
-
-extension StereoCallVC:JoyGemCallStereoDelegate{
- func callManager(_ manager: JoyGemCallManager, didUpdateStereoAsr model: JoyGemAsrModel, channel index: UInt) {
- debugPrint("text: \(model.text),index: \(model.index)")
- if (index == 1) {
- let msg = self.selfMessageList.firstIndex (where: { (str,idx) in
- return idx == model.index;
- })
- if (msg == nil) {
- selfMessageList.append((model.text,model.index))
- } else {
- selfMessageList[msg!] = (model.text,model.index)
- }
-
- selfTextView.text = selfMessageList.compactMap({ (str,idx) in
- return str
- }).joined(separator: "\n")
- selfTextView.scrollToBottom()
- } else if (index == 2) {
- let msg = otherMessageList.firstIndex (where: { (str,idx) in
- return idx == model.index;
- })
- if (msg == nil) {
- otherMessageList.append((model.text,model.index))
- } else {
- otherMessageList[msg!] = (model.text,model.index)
- }
-
- otherTextView.text = otherMessageList.compactMap({ (str,idx) in
- return str
- }).joined(separator: "\n")
- otherTextView.scrollToBottom()
- }
- }
-
- func callManager(_ manager: JoyGemCallManager, stereoIntensity lists: [[NSNumber]], timestamp: TimeInterval) {
-// selfIntensitiesView.intensities = lists[0].map{ num in
-// num.doubleValue
-// }
-// otherIntensitiesView.intensities = lists[1].map{ num in
-// num.doubleValue
-// }
- }
-
-}
fileprivate class DotLabel:BaseView{
let dot = UIView()
make.right.equalToSuperview().inset(36)
}
}
-
}