录音支持后台录音
authorliangzhenying <867174124@qq.com>
Fri, 5 Dec 2025 03:13:30 +0000 (11:13 +0800)
committerliangzhenying <867174124@qq.com>
Fri, 5 Dec 2025 03:13:30 +0000 (11:13 +0800)
180 files changed:
ShenYin/ShenYin.xcodeproj/project.pbxproj
ShenYin/ShenYin/Api/HttpClient2.swift
ShenYin/ShenYin/DeepChordsSdk/DeepChordsSdk.swift [moved from ShenYin/ShenYin/Launch/DeepChordsSdk.swift with 100% similarity]
ShenYin/ShenYin/JoySdk/JoyCallRecord.swift [new file with mode: 0644]
ShenYin/ShenYin/JoySdk/JoyChat.swift [moved from ShenYin/ShenYin/Launch/JoySdkManager.swift with 54% similarity]
ShenYin/ShenYin/JoySdk/JoyRecord.swift [new file with mode: 0644]
ShenYin/ShenYin/JoySdk/JoySdkManager.swift [new file with mode: 0644]
ShenYin/ShenYin/Launch/AppManager.swift
ShenYin/ShenYin/Launch/BleManager.swift
ShenYin/ShenYin/Ota/JLBleAssistManager.h [moved from ShenYin/ShenYin/SDKBleManager/JLBleAssistManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JLBleAssistManager.m [moved from ShenYin/ShenYin/SDKBleManager/JLBleAssistManager.m with 100% similarity]
ShenYin/ShenYin/Ota/JLLogHelper.framework/Headers/JLLogHelper.h [moved from ShenYin/ShenYin/SDKBleManager/JLLogHelper.framework/Headers/JLLogHelper.h with 100% similarity]
ShenYin/ShenYin/Ota/JLLogHelper.framework/Headers/JLLogManager.h [moved from ShenYin/ShenYin/SDKBleManager/JLLogHelper.framework/Headers/JLLogManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JLLogHelper.framework/Info.plist [moved from ShenYin/ShenYin/SDKBleManager/JLLogHelper.framework/Info.plist with 100% similarity]
ShenYin/ShenYin/Ota/JLLogHelper.framework/JLLogHelper [moved from ShenYin/ShenYin/SDKBleManager/JLLogHelper.framework/JLLogHelper with 100% similarity]
ShenYin/ShenYin/Ota/JLLogHelper.framework/Modules/module.modulemap [moved from ShenYin/ShenYin/SDKBleManager/JLLogHelper.framework/Modules/module.modulemap with 100% similarity]
ShenYin/ShenYin/Ota/JL_AdvParse.framework/Frameworks/JLLogHelper.framework/Info.plist [moved from ShenYin/ShenYin/SDKBleManager/JL_AdvParse.framework/Frameworks/JLLogHelper.framework/Info.plist with 100% similarity]
ShenYin/ShenYin/Ota/JL_AdvParse.framework/Frameworks/JLLogHelper.framework/JLLogHelper [moved from ShenYin/ShenYin/SDKBleManager/JL_AdvParse.framework/Frameworks/JLLogHelper.framework/JLLogHelper with 100% similarity]
ShenYin/ShenYin/Ota/JL_AdvParse.framework/Headers/JLAdvParse.h [moved from ShenYin/ShenYin/SDKBleManager/JL_AdvParse.framework/Headers/JLAdvParse.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_AdvParse.framework/Headers/JLChargingBoxAdv.h [moved from ShenYin/ShenYin/SDKBleManager/JL_AdvParse.framework/Headers/JLChargingBoxAdv.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_AdvParse.framework/Headers/JLDevicesAdv.h [moved from ShenYin/ShenYin/SDKBleManager/JL_AdvParse.framework/Headers/JLDevicesAdv.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_AdvParse.framework/Headers/JLEarphoneAdv.h [moved from ShenYin/ShenYin/SDKBleManager/JL_AdvParse.framework/Headers/JLEarphoneAdv.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_AdvParse.framework/Headers/JLOtaAdv.h [moved from ShenYin/ShenYin/SDKBleManager/JL_AdvParse.framework/Headers/JLOtaAdv.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_AdvParse.framework/Headers/JLSoundBoxAdv.h [moved from ShenYin/ShenYin/SDKBleManager/JL_AdvParse.framework/Headers/JLSoundBoxAdv.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_AdvParse.framework/Headers/JLSoundCardAdv.h [moved from ShenYin/ShenYin/SDKBleManager/JL_AdvParse.framework/Headers/JLSoundCardAdv.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_AdvParse.framework/Headers/JLTwsAdv.h [moved from ShenYin/ShenYin/SDKBleManager/JL_AdvParse.framework/Headers/JLTwsAdv.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_AdvParse.framework/Headers/JLWatchAdv.h [moved from ShenYin/ShenYin/SDKBleManager/JL_AdvParse.framework/Headers/JLWatchAdv.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_AdvParse.framework/Headers/JL_AdvParse.h [moved from ShenYin/ShenYin/SDKBleManager/JL_AdvParse.framework/Headers/JL_AdvParse.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_AdvParse.framework/Info.plist [moved from ShenYin/ShenYin/SDKBleManager/JL_AdvParse.framework/Info.plist with 100% similarity]
ShenYin/ShenYin/Ota/JL_AdvParse.framework/JL_AdvParse [moved from ShenYin/ShenYin/SDKBleManager/JL_AdvParse.framework/JL_AdvParse with 100% similarity]
ShenYin/ShenYin/Ota/JL_AdvParse.framework/Modules/module.modulemap [moved from ShenYin/ShenYin/SDKBleManager/JL_AdvParse.framework/Modules/module.modulemap with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Frameworks/JLLogHelper.framework/Info.plist [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Frameworks/JLLogHelper.framework/Info.plist with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Frameworks/JLLogHelper.framework/JLLogHelper [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Frameworks/JLLogHelper.framework/JLLogHelper with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/ECBDTManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/ECBDTManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/ECBDTObjc.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/ECBDTObjc.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/ECOneToMorePtl.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/ECOneToMorePtl.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/ECThreadHelper.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/ECThreadHelper.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL4GUpgradeManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL4GUpgradeManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLAIDialManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLAIDialManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLAiManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLAiManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLAutoConfigAnc.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLAutoConfigAnc.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLAutoPressureModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLAutoPressureModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLCmdBasic.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLCmdBasic.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLConsequentHeartRateModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLConsequentHeartRateModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLDevPlayerCtrl.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLDevPlayerCtrl.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLDeviceConfig.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLDeviceConfig.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLDeviceConfigFuncModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLDeviceConfigFuncModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLDeviceConfigTws.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLDeviceConfigTws.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLDhaFitting.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLDhaFitting.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLDialInfoExtentManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLDialInfoExtentManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLDialInfoExtentedModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLDialInfoExtentedModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLDisconnectRemindModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLDisconnectRemindModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLEmergencyContactModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLEmergencyContactModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLExerciseHeartRateRemindModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLExerciseHeartRateRemindModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLFallDetectionModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLFallDetectionModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLHttpHelper.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLHttpHelper.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModelCardInfo.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModelCardInfo.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModelDevFunc.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModelDevFunc.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModelNoiseDetection.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModelNoiseDetection.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModelSceneNoiseReduction.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModelSceneNoiseReduction.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModelSmartPickFree.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModelSmartPickFree.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModelVocalBoost.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModelVocalBoost.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModel_ANC.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModel_ANC.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModel_AlarmSetting.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModel_AlarmSetting.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModel_BT.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModel_BT.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModel_Device.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModel_Device.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModel_EQ.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModel_EQ.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModel_FM.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModel_FM.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModel_File.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModel_File.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModel_Flash.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModel_Flash.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModel_Headset.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModel_Headset.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModel_RTC.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModel_RTC.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModel_Ring.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModel_Ring.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModel_SPEEX.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModel_SPEEX.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLModel_SmallFile.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLModel_SmallFile.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLOxygenSturationRemindModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLOxygenSturationRemindModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLPersonInfoModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLPersonInfoModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLPublicSetting.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLPublicSetting.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLPublicSettingBlocks.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLPublicSettingBlocks.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLSedentaryRmdModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLSedentaryRmdModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLSensorFuncModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLSensorFuncModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLSleepMonitorModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLSleepMonitorModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLSportDataModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLSportDataModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLWatchEnum.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLWatchEnum.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLWearSync.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLWearSync.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLWearSyncCustom.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLWearSyncCustom.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLWearSyncFinishModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLWearSyncFinishModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLWearSyncInfoModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLWearSyncInfoModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLWearSyncRealTimeModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLWearSyncRealTimeModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLWearable.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLWearable.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLWristLiftDetectionModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLWristLiftDetectionModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_AlarmClockManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_AlarmClockManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_Assist.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_Assist.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_BLEAction.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_BLEAction.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_BLEKit.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_BLEKit.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_BLEMultiple.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_BLEMultiple.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_BatchManger.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_BatchManger.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_BigDataManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_BigDataManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_BinChargeManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_BinChargeManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_CallManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_CallManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_CustomManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_CustomManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_DeviceLogs.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_DeviceLogs.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_EntityM.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_EntityM.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_FileManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_FileManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_FindDeviceManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_FindDeviceManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_FlashOperateManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_FlashOperateManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_FmManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_FmManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_FunctionBaseManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_FunctionBaseManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_Handle.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_Handle.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_LRCManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_LRCManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_LightManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_LightManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_MSG_Func.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_MSG_Func.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_MSG_Weather.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_MSG_Weather.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_ManagerM.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_ManagerM.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_MusicControlManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_MusicControlManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_NFC.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_NFC.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_OpCode.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_OpCode.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_RCSP.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_RCSP.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SDM_AirPressure.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SDM_AirPressure.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SDM_Altitude.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SDM_Altitude.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SDM_Header.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SDM_Header.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SDM_HeartRate.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SDM_HeartRate.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SDM_MaxOxg.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SDM_MaxOxg.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SDM_MoveSteps.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SDM_MoveSteps.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SDM_OxSaturation.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SDM_OxSaturation.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SDM_RecTime.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SDM_RecTime.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SDM_SportMessage.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SDM_SportMessage.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SDM_Stress.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SDM_Stress.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SDM_TrainLoad.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SDM_TrainLoad.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SmallFileManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SmallFileManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SoundCardManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SoundCardManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SpeechAIttsHandler.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SpeechAIttsHandler.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SpeexManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SpeexManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SystemEQ.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SystemEQ.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SystemTime.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SystemTime.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_SystemVolume.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_SystemVolume.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_Tools.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_Tools.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_TwsManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_TwsManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_TypeEnum.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_TypeEnum.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_WatchProtocol.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_WatchProtocol.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_WatchSyncProtocol.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_WatchSyncProtocol.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JL_vad.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JL_vad.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLeSimManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLeSimManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/JLwSettingModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/JLwSettingModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/NSData+ToUnit.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/NSData+ToUnit.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/NSObject+JLTools.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/NSObject+JLTools.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/NfcModel.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/NfcModel.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Headers/RTC_RingInfo.h [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Headers/RTC_RingInfo.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Info.plist [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Info.plist with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/JL_BLEKit [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/JL_BLEKit with 100% similarity]
ShenYin/ShenYin/Ota/JL_BLEKit.framework/Modules/module.modulemap [moved from ShenYin/ShenYin/SDKBleManager/JL_BLEKit.framework/Modules/module.modulemap with 100% similarity]
ShenYin/ShenYin/Ota/JL_HashPair.framework/Frameworks/JLLogHelper.framework/Info.plist [moved from ShenYin/ShenYin/SDKBleManager/JL_HashPair.framework/Frameworks/JLLogHelper.framework/Info.plist with 100% similarity]
ShenYin/ShenYin/Ota/JL_HashPair.framework/Frameworks/JLLogHelper.framework/JLLogHelper [moved from ShenYin/ShenYin/SDKBleManager/JL_HashPair.framework/Frameworks/JLLogHelper.framework/JLLogHelper with 100% similarity]
ShenYin/ShenYin/Ota/JL_HashPair.framework/Headers/BtHash.h [moved from ShenYin/ShenYin/SDKBleManager/JL_HashPair.framework/Headers/BtHash.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_HashPair.framework/Headers/JLHashHandler.h [moved from ShenYin/ShenYin/SDKBleManager/JL_HashPair.framework/Headers/JLHashHandler.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_HashPair.framework/Headers/JL_HashPair.h [moved from ShenYin/ShenYin/SDKBleManager/JL_HashPair.framework/Headers/JL_HashPair.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_HashPair.framework/Headers/JL_ble_pair.h [moved from ShenYin/ShenYin/SDKBleManager/JL_HashPair.framework/Headers/JL_ble_pair.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_HashPair.framework/Info.plist [moved from ShenYin/ShenYin/SDKBleManager/JL_HashPair.framework/Info.plist with 100% similarity]
ShenYin/ShenYin/Ota/JL_HashPair.framework/JL_HashPair [moved from ShenYin/ShenYin/SDKBleManager/JL_HashPair.framework/JL_HashPair with 100% similarity]
ShenYin/ShenYin/Ota/JL_HashPair.framework/Modules/module.modulemap [moved from ShenYin/ShenYin/SDKBleManager/JL_HashPair.framework/Modules/module.modulemap with 100% similarity]
ShenYin/ShenYin/Ota/JL_OTALib.framework/Frameworks/JLLogHelper.framework/Info.plist [moved from ShenYin/ShenYin/SDKBleManager/JL_OTALib.framework/Frameworks/JLLogHelper.framework/Info.plist with 100% similarity]
ShenYin/ShenYin/Ota/JL_OTALib.framework/Frameworks/JLLogHelper.framework/JLLogHelper [moved from ShenYin/ShenYin/SDKBleManager/JL_OTALib.framework/Frameworks/JLLogHelper.framework/JLLogHelper with 100% similarity]
ShenYin/ShenYin/Ota/JL_OTALib.framework/Headers/JLOTAFile.h [moved from ShenYin/ShenYin/SDKBleManager/JL_OTALib.framework/Headers/JLOTAFile.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_OTALib.framework/Headers/JL_OTALib.h [moved from ShenYin/ShenYin/SDKBleManager/JL_OTALib.framework/Headers/JL_OTALib.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_OTALib.framework/Headers/JL_OTAManager.h [moved from ShenYin/ShenYin/SDKBleManager/JL_OTALib.framework/Headers/JL_OTAManager.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_OTALib.framework/Headers/jl_ufw.h [moved from ShenYin/ShenYin/SDKBleManager/JL_OTALib.framework/Headers/jl_ufw.h with 100% similarity]
ShenYin/ShenYin/Ota/JL_OTALib.framework/Info.plist [moved from ShenYin/ShenYin/SDKBleManager/JL_OTALib.framework/Info.plist with 100% similarity]
ShenYin/ShenYin/Ota/JL_OTALib.framework/JL_OTALib [moved from ShenYin/ShenYin/SDKBleManager/JL_OTALib.framework/JL_OTALib with 100% similarity]
ShenYin/ShenYin/Ota/JL_OTALib.framework/Modules/module.modulemap [moved from ShenYin/ShenYin/SDKBleManager/JL_OTALib.framework/Modules/module.modulemap with 100% similarity]
ShenYin/ShenYin/Ota/OtaManager.swift [moved from ShenYin/ShenYin/SDKBleManager/OtaManager.swift with 100% similarity]
ShenYin/ShenYin/UI/Home/CallingRecordVC.swift
ShenYin/ShenYin/UI/Home/Chat/ChatVC.swift
ShenYin/ShenYin/UI/Home/Dialog/DialogRecordBar.swift
ShenYin/ShenYin/UI/Home/FieldRecording/FieldRecordBar.swift
ShenYin/ShenYin/UI/Home/FieldRecording/FieldRecordingVC.swift
ShenYin/ShenYin/UI/Home/FieldRecording/RecordBar.swift
ShenYin/ShenYin/UI/Home/RecordDetail/RecordDetailBar.swift
ShenYin/ShenYin/UI/Home/RecordDetail/RecordDetailTopBar.swift
ShenYin/ShenYin/UI/Home/Root/HomeFunctionView.swift
ShenYin/ShenYin/UI/Home/StereoCallVC.swift

index 23e3245..a618582 100644 (file)
                                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;
index d0559e3..b433f16 100644 (file)
@@ -510,7 +510,7 @@ class HttpClient{
             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 {
@@ -546,7 +546,7 @@ class HttpClient{
             }
             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)
diff --git a/ShenYin/ShenYin/JoySdk/JoyCallRecord.swift b/ShenYin/ShenYin/JoySdk/JoyCallRecord.swift
new file mode 100644 (file)
index 0000000..0326720
--- /dev/null
@@ -0,0 +1,151 @@
+//
+//  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
+        //        }
+    }
+    
+}
similarity index 54%
rename from ShenYin/ShenYin/Launch/JoySdkManager.swift
rename to ShenYin/ShenYin/JoySdk/JoyChat.swift
index 6ac5fde..5ada8b4 100644 (file)
@@ -1,90 +1,37 @@
 //
-//  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) {
@@ -107,9 +54,9 @@ extension JoySdkManager : JoyGemChatManagerDelegate {
                 PcmSound.sharedInstance().setup(16000, withchannels: 1)
                 
                 var options: AVAudioSession.CategoryOptions = [.allowBluetooth]
-//                if App.shared.isInBackground{
-//                    options = [.mixWithOthers]
-//                }
+                //                if App.shared.isInBackground{
+                //                    options = [.mixWithOthers]
+                //                }
                 
                 
                 do {
@@ -144,10 +91,10 @@ extension JoySdkManager : JoyGemChatManagerDelegate {
             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 {
@@ -183,8 +130,8 @@ extension JoySdkManager : JoyGemChatManagerDelegate {
         }
     }
 }
-extension JoySdkManager : PcmSoundDelegate {
-    
+
+extension JoyChat : PcmSoundDelegate {
     func remaining(_ frames: UInt) {
         Logger.debug("[frame:\(frames)]");
         if (frames == 0) {
diff --git a/ShenYin/ShenYin/JoySdk/JoyRecord.swift b/ShenYin/ShenYin/JoySdk/JoyRecord.swift
new file mode 100644 (file)
index 0000000..081566f
--- /dev/null
@@ -0,0 +1,100 @@
+//
+//  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)")
+    }
+}
diff --git a/ShenYin/ShenYin/JoySdk/JoySdkManager.swift b/ShenYin/ShenYin/JoySdk/JoySdkManager.swift
new file mode 100644 (file)
index 0000000..17a6592
--- /dev/null
@@ -0,0 +1,87 @@
+//
+//  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)
+        }
+    }
+}
+
+
index 29be998..4f5a36e 100644 (file)
@@ -24,7 +24,7 @@ class App{
         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())
index 98ef1e0..493adf9 100644 (file)
@@ -280,27 +280,24 @@ extension BleManager : JoyGemAIManagerDelegate {
             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{
@@ -308,14 +305,14 @@ extension BleManager : JoyGemAIManagerDelegate {
                     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())
             }
index 9df93dd..2c4c69a 100644 (file)
@@ -6,35 +6,35 @@
 //
 
 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
         
@@ -67,7 +67,56 @@ class CallingRecordVC:BaseVC{
         }
     }
     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()
         }
@@ -85,9 +134,8 @@ class CallingRecordVC:BaseVC{
         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)
             }
         }
@@ -95,23 +143,6 @@ class CallingRecordVC:BaseVC{
             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()
     }
@@ -122,16 +153,15 @@ class CallingRecordVC:BaseVC{
 
 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()
@@ -139,83 +169,7 @@ extension CallingRecordVC:FieldRecordBarDelegate{
     }
     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) {
-        
-    }
-}
index b09c4dc..b6d491a 100644 (file)
@@ -82,7 +82,7 @@ class ChatVC:BaseVC{
     }
     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:
@@ -95,7 +95,7 @@ class ChatVC:BaseVC{
                 break
             }
         }
-        JoySdkManager.shared.chatNeedReloadData = {[weak self] in
+        JoySdkManager.shared.chat.chatNeedReloadData = {[weak self] in
             guard let self = self else { return  }
             refreshData()
         }
@@ -116,7 +116,7 @@ class ChatVC:BaseVC{
     }
     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") {
@@ -148,8 +148,8 @@ class ChatVC:BaseVC{
     }
     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(){
@@ -186,8 +186,8 @@ extension ChatVC:ChatRecordBarDelegate{
     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()
         }
     }
@@ -211,12 +211,12 @@ extension ChatVC:ChatRecordBarDelegate{
 
 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) {
index 8ec0847..a91be95 100644 (file)
@@ -17,7 +17,7 @@ enum DialogRecordLayoutType{
     case normal
     case spolit
 }
-protocol DialogRecordBarDelegate {
+protocol DialogRecordBarDelegate:NSObject {
     func recordBar(_ bar:DialogRecordBar,layoutChange :DialogRecordLayoutType)
     func recordBar(_ bar:DialogRecordBar, device:DialogRecordDeviceType,stateChange state:RecordBarSate)
 }
@@ -25,7 +25,7 @@ protocol DialogRecordBarDelegate {
 class DialogRecordBar: BaseView {
     let recordView = DialogRecordView()
     let languageView = DialogLanguageView()
-    var delegate:DialogRecordBarDelegate?
+    weak var delegate:DialogRecordBarDelegate?
     var navBar:BaseNavigationBar = BaseNavigationBar()
     
     override func setupViews() {
index 1538fcd..9869983 100644 (file)
@@ -12,7 +12,7 @@ enum RecordSate:Int{
     case pause
 }
 
-protocol FieldRecordBarDelegate {
+protocol FieldRecordBarDelegate:NSObject {
     func fieldRecordBar(_ bar:FieldRecordBar,languageChange language:AiSupportLanguage)
     func fieldRecordBar(_ bar:FieldRecordBar,stateChange state:RecordSate)
 }
@@ -23,7 +23,7 @@ class FieldRecordBar: BaseView {
     let leftItem = RecordBarItem()
     let rightItem = RecordBarButton()
     var state:RecordSate = .stop
-    var delegate:FieldRecordBarDelegate?
+    weak var  delegate:FieldRecordBarDelegate?
     var hasRightItem:Bool = true
     
     override func setupViews() {
index 8d8835d..c44b1a1 100644 (file)
@@ -6,19 +6,21 @@
 //
 
 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() {
@@ -56,8 +58,46 @@ class FieldRecordingVC:BaseVC{
             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])
@@ -69,7 +109,6 @@ class FieldRecordingVC:BaseVC{
             startRecord()
         }
     }
-
     override func viewDidAppear(_ animated: Bool) {
         super.viewDidAppear(animated)
         navigationController?.fd_fullscreenPopGestureRecognizer?.isEnabled = false
@@ -81,8 +120,9 @@ class FieldRecordingVC:BaseVC{
     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)
             }
         }
@@ -90,124 +130,39 @@ class FieldRecordingVC:BaseVC{
             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)")
-    }
-}
index 5fbe576..36c832d 100644 (file)
@@ -10,7 +10,7 @@ enum RecordBarSate:Int{
     case stop
 }
 
-protocol RecordBarDelegate {
+protocol RecordBarDelegate:NSObject {
     func recordBarLanguageChange(_ bar:RecordBar)
     func recordBar(_ bar:RecordBar,stateChange state:RecordBarSate)
 }
@@ -19,7 +19,7 @@ class RecordBar: BaseView {
     let recordBtn = LoadingButton()
     let leftItem = RecordBarItem()
     let rightItem = RecordBarItem()
-    var delegate:RecordBarDelegate?
+    weak var delegate:RecordBarDelegate?
     var isChienseEnglish = false{
         didSet{
             if isChienseEnglish {
index be71f2f..0a22e12 100644 (file)
@@ -5,7 +5,7 @@
 //  Created by liangshi on 2025/9/5.
 //
 
-protocol RecordDetailBarDelegate {
+protocol RecordDetailBarDelegate:NSObject {
     func recordDetailBarTranstlateAction(_ bar:RecordDetailBar)
     func recordDetailBarRenameAction(_ bar:RecordDetailBar)
     func recordDetailBarPlayAction(_ bar:RecordDetailBar)
@@ -18,7 +18,7 @@ class RecordDetailBar: BaseView {
     let playItem = RecordDetailBarButton()
     let moreItem = RecordDetailBarButton()
     
-    var delegate:RecordDetailBarDelegate?
+    weak var delegate:RecordDetailBarDelegate?
     
     override func setupViews() {
         backgroundColor = .white
index 3c6acc1..6fc7f86 100644 (file)
@@ -5,7 +5,7 @@
 //  Created by liangshi on 2025/9/5.
 //
 
-protocol RecordDetailTopBarDelegate {
+@objc protocol RecordDetailTopBarDelegate {
     func recordDetailBarTranstlateAction(_ bar:RecordDetailTopBar)
     func recordDetailTopBarOgrinAction(_ bar:RecordDetailTopBar)
     func recordDetailTopBarSummaryAction(_ bar:RecordDetailTopBar)
@@ -16,7 +16,7 @@ class RecordDetailTopBar: BaseView {
     let originItem = LanguageItem()
     let summaryItem = LanguageItem()
     
-    var delegate:RecordDetailTopBarDelegate?
+    weak var delegate:RecordDetailTopBarDelegate?
     
     override func setupViews() {
         backgroundColor = .clear
index 5e0b350..a6e07e0 100644 (file)
@@ -187,7 +187,7 @@ class HomeCell: BaseView {
         case .fileRecord:
             App.shared.goToPage(FieldRecordingVC())
         case .callRecord:
-            if JoySdkManager.shared.supportStereo{
+            if JoySdkManager.shared.callRecord.supportStereo{
                 App.shared.goToPage(StereoCallVC())
             }
             else{
index 9628e2e..efea135 100644 (file)
@@ -6,7 +6,6 @@
 //
 
 import UIKit
-import JoyGemAI
 import AVFAudio
 class StereoCallVC:BaseVC{
     let toolBar = FieldRecordBar()
@@ -16,15 +15,17 @@ class StereoCallVC:BaseVC{
     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()
@@ -119,13 +120,53 @@ class StereoCallVC:BaseVC{
 
     }
     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
@@ -138,9 +179,8 @@ class StereoCallVC:BaseVC{
         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)
             }
         }
@@ -158,17 +198,17 @@ class StereoCallVC:BaseVC{
 
 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{
@@ -177,7 +217,7 @@ extension StereoCallVC:FieldRecordBarDelegate{
     }
     func stopAction(){
         rhythView.stopTimer()
-        JoyGemCallManager.shared().stop()
+        JoySdkManager.shared.callRecord.callRecordStop()
         toolBar.startAnimating()
     }
     func gotoDetail( fileName:String,wavPath:String,language:AiSupportLanguage){
@@ -194,92 +234,6 @@ extension StereoCallVC:FieldRecordBarDelegate{
         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()
@@ -307,5 +261,4 @@ fileprivate class DotLabel:BaseView{
             make.right.equalToSuperview().inset(36)
         }
     }
-    
 }