static let wxAppID = "wxf19317f23b00d8ab"
static let wxUniversalLink = "https://www.thisiot.com/flychat"
static let wxSecret = "SECRET"
-
+ /// 通用链接
+// https://dc-cloud-pub.tos-cn-guangzhou.volces.com/apple-app-site-association
// static let appStoreLink = "https://itunes.apple.com/cn/app/6738022284?mt=8"
static let appStoreLink = "https://itunes.apple.com/cn/app/6752551504?mt=8"
case .uploadVoiceFile(_):
return "/res/voice-file/upload"
case .getProductList:
- return "/dev/product/list?name=\(BleManager.shared.connectPeripheral?.device.name ?? "")&languageCode=\(DCConfig.appLanguage.lanCode)"
+ return "/dev/product/list?name=&languageCode=\(DCConfig.appLanguage.lanCode)"
case .productBook(let productId):
return "/res/prod-book-file?productId=\(productId)&languageCode=\(DCConfig.appLanguage.lanCode)"
case .qaCategory:
return nil
}
- let filePath = DCFolder.getCacheDirectoryPath()! + "/\(Date().timeIntervalSince1970).png"
+ let filePath = DCFolder.cacheDirectory() + "/\(Date().timeIntervalSince1970).png"
return saveToFile(data: data, filePath: filePath)
}
static func saveToFile(data:Data,filePath:String)->String?{
}
required public init?(coder aDecoder: NSCoder) {
- type = Int(aDecoder.decodeObject(forKey: "type") as! String) ?? 0
+ type = Int(aDecoder.decodeObject(forKey: "type") as? String ?? "0") ?? 0
content = aDecoder.decodeObject(forKey: "content") as? String ?? ""
- id = Int(aDecoder.decodeObject(forKey: "id") as! String) ?? 0
+ id = Int(aDecoder.decodeObject(forKey: "id") as? String ?? "0") ?? 0
}
-
}
class ChatCache {
private let userDefaults = UserDefaults.standard
func saveList(_ list: [ChatCellModel]) {
- var fullList = get()
+// var fullList = get()
+// fullList.append(contentsOf: list)
+ var fullList = [ChatCellModel]()
fullList.append(contentsOf: list)
if fullList.count > 1000{
fullList = Array(fullList.dropFirst(fullList.count - 1000))
}
func get() -> [ChatCellModel] {
- let data = YLToos.readFile(from: DCFolder.chatCache)
- if data == nil{
+
+ guard let data = YLToos.readFile(from: DCFolder.chatCache) else{
return []
}
- if let deviceList = NSKeyedUnarchiver.unarchiveObject(with: data!) as? [ChatCellModel] {
+ if let deviceList = NSKeyedUnarchiver.unarchiveObject(with: data) as? [ChatCellModel] {
return deviceList
}
return []
return path
}
- static func createFolder(_ foldPath:String){
+ @discardableResult
+ static func createFolder(_ foldPath:String)->Bool{
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: foldPath) {
do {
try FileManager.default.createDirectory(atPath: foldPath, withIntermediateDirectories: true, attributes: nil)
+ return true
} catch {
Logger.debug("创建文件夹失败!!!",foldPath)
+ return false
}
}
+ return true
}
// 获取应用沙盒中的缓存目录路径
- static func getCacheDirectoryPath() -> String? {
+ static func cacheDirectory() -> String! {
// 获取缓存目录的URL
if let cacheURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first {
// 转换为路径字符串
return cacheURL.path
}
- return nil
+ return ""
}
static func moveFile(srcPath:String,desPath:String)->Bool{
return false
}
}
+ @discardableResult
+ static func copy(srcPath:String,desPath:String)->Bool{
+ let fileManager = FileManager.default
+
+ do {
+ if fileManager.fileExists(atPath: desPath) {
+ try fileManager.removeItem(atPath: desPath)
+ }
+ try fileManager.copyItem(atPath: srcPath, toPath: desPath)
+ return true
+ } catch {
+ Logger.debug("拷贝文件失败: \(error)")
+ return false
+ }
+ }
+
static func recordFileResave(srcPath:String)->String?{
let desPath = DCFolder.record + (srcPath as NSString).lastPathComponent
let fileManager = FileManager.default
--- /dev/null
+//
+// FileSaver.swift
+// ShenYin
+//
+// Created by liangshi on 2025/10/27.
+//
+
+import ZipArchive
+
+class FileSaver {
+ /// 获取文档目录的URL
+ private static func getDocumentsDirectory() -> String {
+ // 获取缓存目录的URL
+ if let cacheURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first {
+ // 转换为路径字符串
+ return cacheURL.path
+ }
+ return ""
+ }
+
+ /// 将字符串保存为txt文件
+ /// - Parameters:
+ /// - string: 要保存的字符串
+ /// - fileName: 文件名(不包含扩展名)
+ /// - Returns: 是否保存成功
+ static func saveStringAsTxt(_ string: String,fileName:String = "\(Date().format(.full6))") -> String? {
+ let fileURL = getDocumentsDirectory() + "/\(fileName)"
+ do {
+ try string.write(to: URL(fileURLWithPath: fileURL), atomically: true, encoding:.utf8)
+ return fileURL
+ } catch {
+ Logger.debug("保存文件失败: \(error)")
+ return nil
+ }
+ }
+ static func createFolder(_ foldPath:String)->Bool{
+ let fileManager = FileManager.default
+ if fileManager.fileExists(atPath: foldPath) {
+ Logger.debug("文件夹已经存在,先移除",foldPath)
+ do {
+ try FileManager.default.removeItem(atPath: foldPath)
+ } catch {
+ Logger.debug("移除文件夹失败!!!",foldPath)
+ return false
+ }
+ }
+ do {
+ try FileManager.default.createDirectory(atPath: foldPath, withIntermediateDirectories: true, attributes: nil)
+ return true
+ } catch {
+ Logger.debug("创建文件夹失败!!!",foldPath)
+ return false
+ }
+ }
+}
+
+
+extension FileSaver{
+ static func zipFiles(files: [String]) -> String? {
+ let zipArchive = ZipArchive()
+ let des = getDocumentsDirectory() + "/record.zip"
+ if zipArchive.createZipFile2(des) {
+ for fileURL in files {
+
+ if !zipArchive.addFile(toZip: fileURL, newname: URL(fileURLWithPath: fileURL).lastPathComponent ) {
+ if !FileManager.default.fileExists(atPath: fileURL){
+ Logger.debug("文件不存在",fileURL)
+ }
+ Logger.debug("Failed to add file: \(fileURL)")
+ zipArchive.closeZipFile2()
+ return nil
+ }
+ }
+ zipArchive.closeZipFile2()
+ Logger.debug("\(des)")
+ return des
+ } else {
+ Logger.debug("Failed to create zip file")
+ return nil
+ }
+ }
+ static func creatFolder(folderName:String,files: [String]) -> String? {
+ let folder1 = DCFolder.cacheDirectory() + "/\(folderName)"
+ if !createFolder(folder1){
+ return nil
+ }
+ for src in files {
+ let des = folder1 + "/\( URL(fileURLWithPath: src).lastPathComponent)"
+ DCFolder.copy(srcPath: src, desPath: des)
+ }
+
+ return folder1
+ }
+ static func zipFolder(paths:[String])->String?{
+// let zipPath = DCFolder.cacheDirectory() + "/record.zip"
+// let zipArchive = ZipArchive()
+//
+// if zipArchive.createZipFile2(zipPath) {
+// for path in paths {
+// let des = "\( URL(fileURLWithPath: path).lastPathComponent)"
+// Logger.debug("文件夹",path)
+// Logger.debug("新文件夹",des)
+// if zipArchive.addFile(toZip: path, newname: des){
+// Logger.debug("压缩失败",path)
+// }
+//
+// }
+// zipArchive.closeZipFile2()
+// Logger.debug("压缩成功",zipPath)
+// return zipPath
+// } else {
+// Logger.debug("创建压缩文件失败",zipPath)
+// return nil
+// }
+
+ let zipPath = zipFolders(folderPaths: paths)
+ return zipPath
+ }
+
+
+ static func zipFolders(folderPaths: [String]) -> String? {
+ // 获取缓存目录路径
+ guard let cacheDirectory = FileManager.default.urls(for:.cachesDirectory, in:.userDomainMask).first else {
+ print("无法获取缓存目录")
+ return nil
+ }
+ // 构建压缩文件路径
+ let zipFilePath = cacheDirectory.appendingPathComponent("record.zip").path
+
+ let zipArchive = ZipArchive()
+ // 创建zip文件
+ if zipArchive.createZipFile2(zipFilePath) {
+ for folderPath in folderPaths {
+ addFilesFromFolder(folder: folderPath, zipArchive: zipArchive, prefix: folderPath.components(separatedBy: "/").last! + "/")
+ }
+ zipArchive.closeZipFile2()
+ return zipFilePath
+ } else {
+ print("创建zip文件失败")
+ return nil
+ }
+ }
+
+ private static func addFilesFromFolder(folder: String, zipArchive: ZipArchive, prefix: String) {
+ guard let folderURL = URL(string: folder) else {
+ print("无效的文件夹路径: \(folder)")
+ return
+ }
+ let fileManager = FileManager.default
+ do {
+ let files = try fileManager.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: nil)
+ for file in files {
+ let filePath = folderURL.appendingPathComponent(file.lastPathComponent).path
+ let newName = prefix + file.lastPathComponent
+ if !zipArchive.addFile(toZip: filePath, newname: newName) {
+ print("添加文件 \(filePath) 到zip失败")
+ }
+ }
+ } catch {
+ print("读取文件夹 \(folder) 失败: \(error)")
+ }
+ }
+}
+
</array>
<key>NSLocationWhenInUseUsageDescription</key>
<string>允许APP使用定位方便查找耳机</string>
+ <key>ITSAppUsesNonExemptEncryption</key>
+ <false/>
</dict>
</plist>
model.duration = YLToos.getDuration(model.path)
model.origin = textView.text
model.translate = ""
-
+ App.shared.popPage(animated: false)
App.shared.goToPage(RecordDetailVC(record: model))
}
}
let toolBar = ChatRecordBar()
let tableView = UITableView(frame: CGRectZero, style: .plain)
let headerView = ChatHeaderView(frame: CGRect(x: 0, y: 0, width: kScreenWid, height: 260))
- var dataList:[ChatCellModel] = []
+
var roleList:[JoyGemSpeaker] = []
- var isTtsPlaying = false
+
deinit {
PcmSound.sharedInstance().stopAudioUnit()
}
}
override func setupData() {
- JoyGemChatManager.shared().delegate = self
- PcmSound.sharedInstance().delegate = self
JoyGemChatManager.shared().addObserver(self, forKeyPath: "isActive",options: [.new, .old], context: nil);
-
-
-// // 创建一个自定义的串行队列
-// let customQueue = DispatchQueue(label: "com.example.customQueue")
-// customQueue.async {
-// // 这里的代码在子线程中执行
-// print("This is running on a background thread. Thread: \(Thread.current)")
-// // 模拟一些耗时操作
-// Thread.sleep(forTimeInterval: 2)
-// DispatchQueue.main.async {
-// // 回到主线程更新UI等操作
-// print("Back on the main thread. Thread: \(Thread.current)")
-// }
-// }
- YLprogressHUD.showLoading()
- let list = ChatCache.shared.get()
- dataList.append(contentsOf: list)
+ JoySdkManager.shared.chatStateChange = {[weak self] code in
+ guard let self = self else { return }
+ switch code {
+ case .startRecord:
+ toolBar.isRecord = true
+ break
+ case .stopRecord:
+ toolBar.isRecord = false
+ break
+ default:
+ break
+ }
+ }
+ JoySdkManager.shared.chatNeedReloadData = {[weak self] in
+ guard let self = self else { return }
+ refreshData()
+ }
+
refreshData()
- YLprogressHUD.hidden()
-
loadRoleList()
}
override func viewWillDisappear(_ animated: Bool) {
///结束对话,但并不会终止正在播放的录音
JoyGemChatManager.shared().stopChat()
-
- ChatCache.shared.saveList(dataList)
+ ChatCache.shared.saveList(JoySdkManager.shared.dataList)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
func refreshData(){
tableView.reloadData()
- if dataList.count > 0 {
- tableView.scrollToRow(at: IndexPath(row: dataList.count - 1, section: 0), at: .none, animated: true)
+ if JoySdkManager.shared.dataList.count > 0 {
+ tableView.scrollToRow(at: IndexPath(row: JoySdkManager.shared.dataList.count - 1, section: 0), at: .none, animated: true)
}
}
func loadRoleList(){
model.type = 1
model.content = ""
model.id = -1
- dataList.append(model)
+ JoySdkManager.shared.dataList.append(model)
refreshData()
JoyGemChatManager.shared().stopChat()
extension ChatVC:UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return dataList.count
+ return JoySdkManager.shared.dataList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ChatCell.cell(withTableView: tableView, indexPath: indexPath)
- cell.data = dataList[indexPath.row]
+ cell.data = JoySdkManager.shared.dataList[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}
-extension ChatVC : JoyGemChatManagerDelegate {
- func chatManager(_ manager: JoyGemChatManager, audioData data: Data?, index: UInt, sampleRate: UInt, finish isFinish: Bool) {
- if (!isTtsPlaying) {
- isTtsPlaying = true
- Logger.debug("开始播放")
- JoyGemChatManager.shared().playAudioStart()
- }
- Logger.debug("收到音频数据", data?.count ?? 0)
- PcmSound.sharedInstance().feed(data)
- }
-
- func chatManager(_ manager: JoyGemChatManager, stateChange code: JoyGemStateCode) {
- Logger.debug("事件 ",code)
- switch code {
- case .startRecord:
- toolBar.isRecord = true
- if (isTtsPlaying) {
- isTtsPlaying = false
- 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)
- }
- }
-// stateLabel.text = "正在录音"
- break
- case .stopRecord:
- toolBar.isRecord = false
- break
- default:
- break
- }
- }
- func chatManager(_ manager: JoyGemChatManager, eventUpdate event: JoyGemChatEvent) {
- Logger.debug("事件改变",event)
- }
-
- func chatManager(_ manager: JoyGemChatManager, onError code: JoyGemErrorCode) {
- Logger.debug("收到错误",code)
- }
-
- func chatManager(_ manager: JoyGemChatManager, audioData data: Data?, finish isFinish: Bool) {
-
- }
-
- func chatManager(_ manager: JoyGemChatManager, requestText text: String?, index: UInt) {
- Logger.debug(text,index)
- guard let text = text else{
- return
- }
- if text.isEmpty {
- return
- }
- if dataList.last?.id == -1 {
- dataList.removeLast()
- }
- if let model = dataList.last,model.id == index,model.type == 1 {
- model.content = text
- }
- else{
- let model = ChatCellModel()
- model.type = 1
- model.content = text
- model.id = Int(index)
- dataList.append(model)
- }
- refreshData()
- }
-
- func chatManager(_ manager: JoyGemChatManager, responseText text: String?, index: UInt, finish isFinish: Bool) {
-// responseTextView.text = text;
- Logger.debug(text,index)
- if let text = text {
- if let model = dataList.last,model.id == index,model.type == 0 {
- model.content = text
- }
- else{
- let model = ChatCellModel()
- model.type = 0
- model.content = text
- model.id = Int(index)
- dataList.append(model)
- }
- refreshData()
- }
- }
-}
-extension ChatVC : PcmSoundDelegate {
-
- func remaining(_ frames: UInt) {
- Logger.debug("[frame:\(frames)]");
- if (frames == 0) {
- JoyGemChatManager.shared().playAudioEnd()
- }
- }
-}
for item in DeviceVM.shared.dataList {
if item.mac == lastMac{
self?.startConectBle(item)
+ return
}
}
}
}
override func setupData() {
JoyGemRecordManager.shared().delegate = self
- PcmSound.sharedInstance().delegate = self;
- PcmSound.sharedInstance().setup(16000, withchannels: 1)
let options: AVAudioSession.CategoryOptions = [.allowBluetooth]
do {
try AVAudioSession.sharedInstance().setCategory(.playback, options: [options])
CustomAlert.showWith(title: "提示", messgage: "当前正在录音,确定放弃当前录音吗?".l10n, cancelCallback: {}) {[unowned self] in
self.rhythView.stopTimer()
JoyGemRecordManager.shared().stop()
- PcmSound.sharedInstance().stopAudioUnit()
- PcmSound.sharedInstance().cleanup()
self.navigationController?.popViewController(animated: true)
}
}
model.duration = YLToos.getDuration(model.path)
model.origin = textView.text
model.translate = ""
-
+ App.shared.popPage(animated: false)
App.shared.goToPage(RecordDetailVC(record: model))
}
}
extension FieldRecordingVC : JoyGemRecordManagerDelegate {
func recordManager(_ manager: JoyGemRecordManager, didUpdateAudioData data: Data) {
- PcmSound.sharedInstance().feed(data)
+// PcmSound.sharedInstance().feed(data)
}
func recordManager(_ manager: JoyGemRecordManager, didUpdateTranslate model: JoyGemAsrModel) {
if (code == .actively) {
// YLprogressHUD.showAutoHiden("用户主动结束")
// toolBar.recordBtn.stopLoading()
-// let v = PopTextField()
-// v.show()
-// v.cancelTap = {
-//
-// }
-// v.okTap = {
-//
-// }
} else {
// YLprogressHUD.showAutoHiden("自动结束")
///判断当前蓝牙是否连接从而确定是否因为蓝牙断开造成的录音断开
Logger.debug("onError: \(code)")
}
}
-
-extension FieldRecordingVC : PcmSoundDelegate {
-
- func remaining(_ frames: UInt) {
- Logger.debug("[frame:\(frames)]");
- }
-}
+++ /dev/null
-//
-// FileSaver.swift
-// ShenYin
-//
-// Created by liangshi on 2025/10/27.
-//
-
-import ZipArchive
-
-class FileSaver {
- /// 获取文档目录的URL
- private static func getDocumentsDirectory() -> URL {
- let paths = FileManager.default.urls(for:.cachesDirectory, in:.userDomainMask)
- return paths[0]
- }
-
- /// 将字符串保存为txt文件
- /// - Parameters:
- /// - string: 要保存的字符串
- /// - fileName: 文件名(不包含扩展名)
- /// - Returns: 是否保存成功
- static func saveStringAsTxt(_ string: String,fileName:String = "\(Date().format(.full6))") -> String? {
- let fileURL = getDocumentsDirectory().appendingPathComponent(fileName).appendingPathExtension("txt")
- do {
- try string.write(to: fileURL, atomically: true, encoding:.utf8)
- return fileURL.path
- } catch {
- Logger.debug("Error saving file: \(error)")
- return nil
- }
- }
-
-}
-
-
-extension FileSaver{
- static func zipFiles(files: [String]) -> String? {
- let zipArchive = ZipArchive()
- let des = getDocumentsDirectory().appendingPathComponent("\(Date().format(.full5))").appendingPathExtension("zip").path
- if zipArchive.createZipFile2(des) {
- for fileURL in files {
-
- if !zipArchive.addFile(toZip: fileURL, newname: URL(fileURLWithPath: fileURL).lastPathComponent ) {
- if !FileManager.default.fileExists(atPath: fileURL){
- Logger.debug("文件不存在",fileURL)
- }
- Logger.debug("Failed to add file: \(fileURL)")
- zipArchive.closeZipFile2()
- return nil
- }
- }
- zipArchive.closeZipFile2()
- Logger.debug("\(des)")
- return des
- } else {
- Logger.debug("Failed to create zip file")
- return nil
- }
- }
-}
-
override func setupData() {
}
- func loadFile(filedId id:String){
- let api = CommomApi.recordUrl(filedId: id)
- HttpClient.request(api) {[weak self] (model:CommonDataModel,res) in
- guard let self = self else {return}
- if model.isSuccess{
- startDownload(url: model.data)
+
+
+ @objc func btnAction(){
+ if recordList.count > 0 {
+ startShare()
+ }
+ else{
+ YLprogressHUD.showLoading()
+ loadDetails {[weak self] result in
+ guard let self = self else{return}
+ if result{
+ YLprogressHUD.hidden()
+ startShare()
+ }
+ else{
+ YLprogressHUD.showAutoHiden("请求失败".l10n)
+ }
}
}
}
- func startDownload(url:String){
- // let url = "https://dc-cloud-priv.tos-cn-guangzhou.volces.com/music-file/2025_10_09/1976135290430767105.mp3"
-// HttpClient.download(url: url,type: "wav"){[weak self] result in
-// guard let self = self else {return}
-// if let path = result{
-// recordModel.path = path
-// }
-// }
- }
- @objc func btnAction(){
- if recordList.count == 1,let recordModel = recordList.first {
- if currentType == .audio {
+ func startShare(){
+ var folders = [String]()
+ for recordModel in recordList {
+ var list = [String]()
+
+ if currentType == .audio_document || currentType == .audio{
if recordModel.path.isNotEmpty {
- saveFile(filePath: recordModel.path)
+ let des = DCFolder.cacheDirectory() + "/audio.wav"
+ if DCFolder.copy(srcPath: recordModel.path, desPath: des) {
+ list.append(des)
+ }
}
}
- else if currentType == .document{
- guard let path = FileSaver.saveStringAsTxt(recordModel.origin,fileName: recordModel.name + "") else {
- return
+
+ if currentType == .document || currentType == .audio_document{
+ if let orginPath = FileSaver.saveStringAsTxt(recordModel.origin ,fileName: "original.txt") {
+ list.append(orginPath)
}
- saveFile(filePath: path)
- }
- else{
-
- guard let orginPath = FileSaver.saveStringAsTxt(recordModel.origin ,fileName: recordModel.name + "") else {
- return
- }
- var list = [orginPath]
- if recordModel.path.isNotEmpty {
- list.append(recordModel.path)
- }
-
- if recordModel.translate.count > 0,let path = FileSaver.saveStringAsTxt(recordModel.translate ,fileName: recordModel.name + "_translate") {
+ if recordModel.translate.count > 0,let path = FileSaver.saveStringAsTxt(recordModel.translate ,fileName: "translate.txt") {
list.append(path)
}
- if recordModel.summary.count > 0,let path = FileSaver.saveStringAsTxt(recordModel.summary ,fileName: recordModel.name + "_summary") {
+ if recordModel.summary.count > 0,let path = FileSaver.saveStringAsTxt(recordModel.summary ,fileName: "summary.txt") {
list.append(path)
}
- guard let path = FileSaver.zipFiles(files: list) else { return }
-
- saveFile(filePath: path)
}
+
+
+ if let folder = FileSaver.creatFolder(folderName: recordModel.name + "_" + recordModel.createTime.components(separatedBy: CharacterSet(charactersIn: " -/")).joined(), files: list){
+ folders.append(folder)
+ }
+ Logger.debug(list)
+ }
+ Logger.debug(folders)
+ if let path = FileSaver.zipFolder(paths: folders){
+ // saveFile(filePath: path)
+ let vc = UIActivityViewController(activityItems: [URL(fileURLWithPath: path)], applicationActivities: nil)
+ vc.modalPresentationStyle = .overFullScreen
+ App.shared.topVC().present(vc, animated: true)
}
}
}
-extension RecordShareVC:UIDocumentPickerDelegate{
- //保存文件到手机文件指定目录
- func saveFile(filePath:String) {
- if !FileManager.default.fileExists(atPath: filePath){
- Logger.debug("文件不存在",filePath)
- return
+extension RecordShareVC{
+ func loadDetails(_ finished:@escaping ((Bool) -> Void)){
+ recordList.removeAll()
+ let dispatchGroup = DispatchGroup()
+ for item in cloudList {
+ dispatchGroup.enter()
+ requestDetail(item) {[weak self] model in
+ guard let self = self else { return }
+ if let data = model{
+ recordList.append(data)
+ }
+ dispatchGroup.leave()
+ }
}
- let fUrl = NSURL(fileURLWithPath: filePath)
- let vc = UIDocumentPickerViewController(url: fUrl as URL, in: .exportToService);
- vc.delegate = self
- vc.modalPresentationStyle = .overFullScreen
- self.present(vc, animated: true)
+ dispatchGroup.notify(queue:.main) {
+ finished(true)
+ }
}
- public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
- controller.dismiss(animated: true)
+
+ func requestDetail(_ remoteData:RecordHistoryModel,finished:@escaping ((RecordDetailModel?) -> Void)){
+ let api = CommomApi.getVoiceRecord(recordId: remoteData.id)
+ HttpClient.request(api) {[weak self](result:RecordHistoryDetailRoot,res) in
+ if result.isSuccess{
+ let data = result.data
+ let model = RecordDetailModel()
+ model.type = data.type
+ model.origin = data.summary
+ model.translate = ""
+ model.path = ""
+ model.name = data.name
+ model.createTime = data.createTime
+ self?.requestVoiceRecordText(remoteModel: remoteData, localModel: model,finished: finished)
+ }
+ else{
+ finished(nil)
+ }
+ }
+ }
+ func requestVoiceRecordText(remoteModel:RecordHistoryModel,localModel:RecordDetailModel,finished:@escaping ((RecordDetailModel?) -> Void)){
+ var params:[String:Any] = [:]
+ params["recordId"] = remoteModel.id
+ params["page"] = 1
+ let api = CommomApi.getVoiceRecordText(param: params)
+ HttpClient.request(api) {[weak self](result:CloudRecordDetailRoot,res) in
+ if result.isSuccess,let model = result.data.list.first{
+ localModel.origin = model.text
+ localModel.translate = model.translateText
+ localModel.from = AiSupportLanguage(rawValue: model.sourceLanguage) ?? .chinse
+ localModel.to = AiSupportLanguage(rawValue: model.targetLanguage) ?? .english
+
+ self?.getRecordPath(filedId: remoteModel.fileId) { result in
+ if let path = result{
+ localModel.path = path
+ }
+ finished(localModel)
+ }
+
+ }
+ else{
+ finished(nil)
+ }
+ }
+ }
+ func getRecordPath(filedId id:String,finished:@escaping ((String?) -> Void)){
+ let api = CommomApi.recordUrl(filedId: id)
+ HttpClient.request(api) {[weak self] (model:CommonDataModel,res) in
+ guard let self = self else {return}
+ if model.isSuccess{
+ startDownload(url: model.data,finished: finished)
+ }
+ else{
+ finished(nil)
+ }
+ }
+ }
+ func startDownload(url:String,finished:@escaping ((String?) -> Void)){
+ HttpClient.download(url: url,type: "wav"){result in
+ finished(result)
+ }
}
}
+
extension RecordShareVC:UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
translateView.textView.font = FontCache.getFont().font
summaryView.textView.font = FontCache.getFont().font
}
- override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
- if needFrontVC == false {
- var list = self.navigationController!.viewControllers
- list.remove(at: list.count - 2)
- self.navigationController!.viewControllers = list
- }
- }
+
+// override func viewDidAppear(_ animated: Bool) {
+// super.viewDidAppear(animated)
+// if needFrontVC == false {
+// var list = self.navigationController!.viewControllers
+// list.remove(at: list.count - 2)
+// self.navigationController!.viewControllers = list
+// }
+// }
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if remoteData == nil,let localData = data {
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))
}
}
// Logger.verbose("注册远程通知发生错误:", error)
// }
//
-// func applicationDidEnterBackground(_ application: UIApplication) {
-// DB.shared.save()
-// JPush.shared.tellAppState(0)
-// Logger.verbose("App 进入后台")
-// }
-//
-// func applicationWillEnterForeground(_ application: UIApplication) {
-// JPush.shared.tellAppState(1)
-// Logger.verbose("App 将要进入前台")
-// }
+
//
// private var previousRefreshTime: TimeInterval = 0.0
// func applicationDidBecomeActive(_ application: UIApplication) {
class App{
static let shared = App()
var window:UIWindow?
+ var isInBackground = false
+
func topVC()->UIViewController!{
guard let vc = window?.rootViewController else{
Logger.debug(DCFolder.user)
Logger.debug(DCFolder.record)
window?.rootViewController = BaseTC()
+ JoySdkManager.shared.loadChatCache()
}
func goToLogin(){
window?.rootViewController = BaseNC(rootViewController: LoginVC())
}
case .recordLive:
Logger.debug("请求开启现场录音")
+ if App.shared.isInBackground {
+ return
+ }
// YLprogressHUD.showAutoHiden("请求开启现场录音")
- if !App.shared.topVC().isKind(of: FieldRecordingVC.self) {
+ if let vc = App.shared.topVC() as? FieldRecordingVC {
+ vc.startRecord()
+ }
+ else{
App.shared.goToPage(FieldRecordingVC(autoStart: true))
}
case .recordCall:
Logger.debug("请求开启通话录音")
+ if App.shared.isInBackground {
+ return
+ }
// YLprogressHUD.showAutoHiden("请求开启通话录音")
- if !App.shared.topVC().isKind(of: CallingRecordVC.self) {
+ if let vc = App.shared.topVC() as? CallingRecordVC {
+ vc.startRecord()
+ }else{
App.shared.goToPage(CallingRecordVC(autoStart: true))
}
case .si:
Logger.debug( "请求开启同传听译")
-// YLprogressHUD.showAutoHiden( "请求开启同传听译")
case .chat:
Logger.debug("通知开启了AI对话")
// YLprogressHUD.showAutoHiden( "通知开启了AI对话")
class JoySdkManager:NSObject{
static let shared = JoySdkManager()
+ var isTtsPlaying = false
+ var dataList:[ChatCellModel] = []
+ var chatStateChange:((JoyGemStateCode)->Void)?
+ var chatNeedReloadData:(()->Void)?
+}
+extension JoySdkManager {
func setupSdk(){
//MARK: 初始化SDK
JoyGemAIManager.shared().initSdk()
//MARK: 配置日志类型
-// JoyGemAIManager.shared().setLogMode(JoyGemLogMode.all)
+ // JoyGemAIManager.shared().setLogMode(JoyGemLogMode.all)
JoyGemAIManager.shared().setLogMode(JoyGemLogMode.close)
let fileManager = FileManager.default
let tempPath = NSTemporaryDirectory();
JoyGemAIManager.shared().setCacheDirectory(tempPath)
JoyGemAIManager.shared().setTWSAutoSync(true)
+
+ JoyGemChatManager.shared().delegate = self
}
}
+
+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) {
+ isTtsPlaying = true
+ Logger.debug("开始播放")
+ JoyGemChatManager.shared().playAudioStart()
+ }
+ Logger.debug("收到音频数据", data?.count ?? 0)
+ PcmSound.sharedInstance().feed(data)
+ }
+
+ func chatManager(_ manager: JoyGemChatManager, stateChange code: JoyGemStateCode) {
+ chatStateChange?(code)
+ Logger.debug("事件 ",code)
+ switch code {
+ case .startRecord:
+ if (isTtsPlaying) {
+ isTtsPlaying = false
+ 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)
+ }
+ }
+ // stateLabel.text = "正在录音"
+ break
+ case .stopRecord:
+ break
+ default:
+ break
+ }
+ }
+ func chatManager(_ manager: JoyGemChatManager, eventUpdate event: JoyGemChatEvent) {
+ Logger.debug("事件改变",event)
+ }
+
+ func chatManager(_ manager: JoyGemChatManager, onError code: JoyGemErrorCode) {
+ Logger.debug("收到错误",code)
+ }
+
+ func chatManager(_ manager: JoyGemChatManager, audioData data: Data?, finish isFinish: Bool) {
+
+ }
+
+ func chatManager(_ manager: JoyGemChatManager, requestText text: String?, index: UInt) {
+ Logger.debug(text,index)
+ guard let text = text else{
+ return
+ }
+ if text.isEmpty {
+ return
+ }
+ if dataList.last?.id == -1 {
+ dataList.removeLast()
+ }
+ if let model = dataList.last,model.id == index,model.type == 1 {
+ model.content = text
+ }
+ else{
+ let model = ChatCellModel()
+ model.type = 1
+ model.content = text
+ model.id = Int(index)
+ dataList.append(model)
+ }
+ chatNeedReloadData?()
+ }
+
+ func chatManager(_ manager: JoyGemChatManager, responseText text: String?, index: UInt, finish isFinish: Bool) {
+ Logger.debug(text,index)
+ if let text = text {
+ if let model = dataList.last,model.id == index,model.type == 0 {
+ model.content = text
+ }
+ else{
+ let model = ChatCellModel()
+ model.type = 0
+ model.content = text
+ model.id = Int(index)
+ dataList.append(model)
+ }
+ chatNeedReloadData?()
+ }
+ }
+}
+extension JoySdkManager : PcmSoundDelegate {
+
+ func remaining(_ frames: UInt) {
+ Logger.debug("[frame:\(frames)]");
+ if (frames == 0) {
+ JoyGemChatManager.shared().playAudioEnd()
+ }
+ }
+}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
+ App.shared.isInBackground = false
}
func sceneWillResignActive(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
+ App.shared.isInBackground = true
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
else{
var list = [InstructionsModel]()
for item in orginList {
- if item.name.contains(text) {
+ if item.name.uppercased() .contains(text.uppercased()) {
list.append(item)
}
}
case Singlish
static var all:[AppSupportLanguage]{
- return [.chinse,.Traditional_Chinese,.english,.japanese,.korean,.spanish,.vietnam,.thai,.malay,.Filipino,.indonesian,.french,.spanish,.russian,]
+ return [.chinse,.Traditional_Chinese,.english,.japanese,.korean,.spanish,.vietnam,.thai,.malay,.Filipino,.indonesian,.french,.Singlish,.russian]
}
var title:String{
switch self {