From ce889e1ed0f431a743421a3a8176cd1f95be87dc Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Tue, 27 Jan 2026 13:57:32 +0800 Subject: [PATCH] 1 --- Shared/KBAPI.h | 1 + keyBoard.xcodeproj/project.pbxproj | 6 + keyBoard/Class/AiTalk/VC/KBAIHomeVC.m | 50 ++- keyBoard/Class/AiTalk/VM/AiVM.h | 6 + keyBoard/Class/AiTalk/VM/AiVM.m | 45 +++ .../Class/AiTalk/VM/KBVoiceRecordManager.h | 47 +++ .../Class/AiTalk/VM/KBVoiceRecordManager.m | 289 ++++++++++++++++++ .../Class/AiTalk/VM/KBVoiceToTextManager.h | 1 + .../Class/AiTalk/VM/KBVoiceToTextManager.m | 58 +++- 9 files changed, 497 insertions(+), 6 deletions(-) create mode 100644 keyBoard/Class/AiTalk/VM/KBVoiceRecordManager.h create mode 100644 keyBoard/Class/AiTalk/VM/KBVoiceRecordManager.m diff --git a/Shared/KBAPI.h b/Shared/KBAPI.h index b5449c6..1173ce0 100644 --- a/Shared/KBAPI.h +++ b/Shared/KBAPI.h @@ -72,6 +72,7 @@ #define API_AI_VOICE_TALK @"/chat/voice" // 语音对话(替换为后端真实路径) #define API_AI_CHAT_SYNC @"/chat/sync" // 同步对话 #define API_AI_CHAT_MESSAGE @"/chat/message" // 文本润色 +#define API_AI_AUDIO_UPLOAD @"/chat/audio/upload" // 语音上传(替换为后端真实路径) diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 7682a58..1848db9 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -209,6 +209,7 @@ 04E038E82F20E877002CA5A0 /* DeepgramWebSocketClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E038E72F20E877002CA5A0 /* DeepgramWebSocketClient.m */; }; 04E038E92F20E877002CA5A0 /* DeepgramStreamingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E038E52F20E877002CA5A0 /* DeepgramStreamingManager.m */; }; 04E0B1022F300001002CA5A0 /* KBVoiceToTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E0B1012F300001002CA5A0 /* KBVoiceToTextManager.m */; }; + 04E0B2022F300002002CA5A0 /* KBVoiceRecordManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E0B2012F300002002CA5A0 /* KBVoiceRecordManager.m */; }; 04E038EF2F21F0EC002CA5A0 /* AiVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E038EE2F21F0EC002CA5A0 /* AiVM.m */; }; 04E0394B2F236E75002CA5A0 /* KBChatUserMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E0394A2F236E75002CA5A0 /* KBChatUserMessageCell.m */; }; 04E0394C2F236E75002CA5A0 /* KBChatTimeCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E039482F236E75002CA5A0 /* KBChatTimeCell.m */; }; @@ -653,6 +654,8 @@ 04E038E52F20E877002CA5A0 /* DeepgramStreamingManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DeepgramStreamingManager.m; sourceTree = ""; }; 04E0B1002F300001002CA5A0 /* KBVoiceToTextManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBVoiceToTextManager.h; sourceTree = ""; }; 04E0B1012F300001002CA5A0 /* KBVoiceToTextManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBVoiceToTextManager.m; sourceTree = ""; }; + 04E0B2002F300002002CA5A0 /* KBVoiceRecordManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBVoiceRecordManager.h; sourceTree = ""; }; + 04E0B2012F300002002CA5A0 /* KBVoiceRecordManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBVoiceRecordManager.m; sourceTree = ""; }; 04E038E62F20E877002CA5A0 /* DeepgramWebSocketClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeepgramWebSocketClient.h; sourceTree = ""; }; 04E038E72F20E877002CA5A0 /* DeepgramWebSocketClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DeepgramWebSocketClient.m; sourceTree = ""; }; 04E038ED2F21F0EC002CA5A0 /* AiVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AiVM.h; sourceTree = ""; }; @@ -1079,6 +1082,8 @@ 04E038E52F20E877002CA5A0 /* DeepgramStreamingManager.m */, 04E0B1002F300001002CA5A0 /* KBVoiceToTextManager.h */, 04E0B1012F300001002CA5A0 /* KBVoiceToTextManager.m */, + 04E0B2002F300002002CA5A0 /* KBVoiceRecordManager.h */, + 04E0B2012F300002002CA5A0 /* KBVoiceRecordManager.m */, 04E038E62F20E877002CA5A0 /* DeepgramWebSocketClient.h */, 04E038E72F20E877002CA5A0 /* DeepgramWebSocketClient.m */, 04E038ED2F21F0EC002CA5A0 /* AiVM.h */, @@ -2331,6 +2336,7 @@ 04E038E82F20E877002CA5A0 /* DeepgramWebSocketClient.m in Sources */, 04E038E92F20E877002CA5A0 /* DeepgramStreamingManager.m in Sources */, 04E0B1022F300001002CA5A0 /* KBVoiceToTextManager.m in Sources */, + 04E0B2022F300002002CA5A0 /* KBVoiceRecordManager.m in Sources */, 048908E32EBF821700FABA60 /* KBSkinDetailVC.m in Sources */, 0477BDF32EBB7B850055D639 /* KBDirectionIndicatorView.m in Sources */, 048FFD142F274342005D62AE /* KBPersonaChatCell.m in Sources */, diff --git a/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m b/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m index a58a60e..a19132a 100644 --- a/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m +++ b/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m @@ -9,11 +9,13 @@ #import "KBPersonaChatCell.h" #import "KBPersonaModel.h" #import "KBVoiceInputBar.h" +#import "KBVoiceRecordManager.h" #import "KBVoiceToTextManager.h" #import "AiVM.h" +#import "KBHUD.h" #import -@interface KBAIHomeVC () +@interface KBAIHomeVC () /// 人设列表容器 @property (nonatomic, strong) UICollectionView *collectionView; @@ -24,6 +26,9 @@ /// 语音转写管理器 @property (nonatomic, strong) KBVoiceToTextManager *voiceToTextManager; +/// 录音管理器 +@property (nonatomic, strong) KBVoiceRecordManager *voiceRecordManager; + /// 人设数据 @property (nonatomic, strong) NSMutableArray *personas; @@ -66,6 +71,7 @@ [self setupUI]; [self setupVoiceToTextManager]; + [self setupVoiceRecordManager]; [self loadPersonas]; } @@ -245,9 +251,17 @@ - (void)setupVoiceToTextManager { self.voiceToTextManager = [[KBVoiceToTextManager alloc] initWithInputBar:self.voiceInputBar]; self.voiceToTextManager.delegate = self; + self.voiceToTextManager.deepgramEnabled = NO; [self.voiceToTextManager prepareConnection]; } +/// 5:录音管理 +- (void)setupVoiceRecordManager { + self.voiceRecordManager = [[KBVoiceRecordManager alloc] init]; + self.voiceRecordManager.delegate = self; + self.voiceRecordManager.minRecordDuration = 1.0; +} + - (NSInteger)currentCompanionId { if (self.personas.count == 0) { return 0; @@ -381,4 +395,38 @@ NSLog(@"[KBAIHomeVC] 语音识别失败:%@", error.localizedDescription); } +- (void)voiceToTextManagerDidBeginRecording:(KBVoiceToTextManager *)manager { + [self.voiceRecordManager startRecording]; +} + +- (void)voiceToTextManagerDidEndRecording:(KBVoiceToTextManager *)manager { + [self.voiceRecordManager stopRecording]; +} + +- (void)voiceToTextManagerDidCancelRecording:(KBVoiceToTextManager *)manager { + [self.voiceRecordManager cancelRecording]; +} + +#pragma mark - KBVoiceRecordManagerDelegate + +- (void)voiceRecordManager:(KBVoiceRecordManager *)manager + didFinishRecordingAtURL:(NSURL *)fileURL + duration:(NSTimeInterval)duration { + NSDictionary *attributes = [[NSFileManager defaultManager] + attributesOfItemAtPath:fileURL.path + error:nil]; + unsigned long long fileSize = [attributes[NSFileSize] unsignedLongLongValue]; + NSLog(@"[KBAIHomeVC] 录音完成,时长: %.2fs,大小: %llu bytes", duration, fileSize); +} + +- (void)voiceRecordManagerDidRecordTooShort:(KBVoiceRecordManager *)manager { + NSLog(@"[KBAIHomeVC] 录音过短,已忽略"); + [KBHUD showError:KBLocalized(@"录音时间过短,请重新录音")]; +} + +- (void)voiceRecordManager:(KBVoiceRecordManager *)manager + didFailWithError:(NSError *)error { + NSLog(@"[KBAIHomeVC] 录音失败:%@", error.localizedDescription); +} + @end diff --git a/keyBoard/Class/AiTalk/VM/AiVM.h b/keyBoard/Class/AiTalk/VM/AiVM.h index bfbc15a..af09f21 100644 --- a/keyBoard/Class/AiTalk/VM/AiVM.h +++ b/keyBoard/Class/AiTalk/VM/AiVM.h @@ -44,6 +44,8 @@ typedef void (^AiVMMessageCompletion)(KBAiMessageResponse *_Nullable response, typedef void (^AiVMAudioURLCompletion)(NSString *_Nullable audioURL, NSError *_Nullable error); +typedef void (^AiVMUploadAudioCompletion)(NSString *_Nullable fileURL, + NSError *_Nullable error); @interface AiVM : NSObject @@ -58,6 +60,10 @@ typedef void (^AiVMAudioURLCompletion)(NSString *_Nullable audioURL, - (void)requestAudioWithAudioId:(NSString *)audioId completion:(AiVMAudioURLCompletion)completion; +/// 上传语音文件(m4a) +- (void)uploadAudioFileAtURL:(NSURL *)fileURL + completion:(AiVMUploadAudioCompletion)completion; + #pragma mark - 人设相关接口 /// 分页查询人设列表 diff --git a/keyBoard/Class/AiTalk/VM/AiVM.m b/keyBoard/Class/AiTalk/VM/AiVM.m index b03b6dd..510237f 100644 --- a/keyBoard/Class/AiTalk/VM/AiVM.m +++ b/keyBoard/Class/AiTalk/VM/AiVM.m @@ -216,6 +216,51 @@ autoShowBusinessError:NO }]; } +- (void)uploadAudioFileAtURL:(NSURL *)fileURL + completion:(AiVMUploadAudioCompletion)completion { + if (!fileURL || !fileURL.isFileURL) { + NSError *error = [NSError errorWithDomain:@"AiVM" + code:-1 + userInfo:@{NSLocalizedDescriptionKey : @"invalid fileURL"}]; + if (completion) { + completion(nil, error); + } + return; + } + + [[KBNetworkManager shared] uploadFile:API_AI_AUDIO_UPLOAD + fileURL:fileURL + name:@"file" + mimeType:@"audio/mp4" + parameters:nil + headers:nil + completion:^(NSDictionary *_Nullable json, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + if (completion) { + completion(nil, error); + } + return; + } + + NSString *fileURLString = nil; + id dataObj = json[@"data"]; + if ([dataObj isKindOfClass:[NSString class]]) { + fileURLString = (NSString *)dataObj; + } else if ([dataObj isKindOfClass:[NSDictionary class]]) { + id urlObj = dataObj[@"url"] ?: dataObj[@"audioUrl"]; + if ([urlObj isKindOfClass:[NSString class]]) { + fileURLString = (NSString *)urlObj; + } + } + + if (completion) { + completion(fileURLString, nil); + } + }]; +} + #pragma mark - 人设相关接口 - (void)fetchPersonasWithPageNum:(NSInteger)pageNum diff --git a/keyBoard/Class/AiTalk/VM/KBVoiceRecordManager.h b/keyBoard/Class/AiTalk/VM/KBVoiceRecordManager.h new file mode 100644 index 0000000..113597c --- /dev/null +++ b/keyBoard/Class/AiTalk/VM/KBVoiceRecordManager.h @@ -0,0 +1,47 @@ +// +// KBVoiceRecordManager.h +// keyBoard +// +// Created by Mac on 2026/1/26. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, KBVoiceRecordManagerErrorCode) { + KBVoiceRecordManagerErrorPermissionDenied = 1, + KBVoiceRecordManagerErrorSessionFailed = 2, + KBVoiceRecordManagerErrorRecorderFailed = 3, + KBVoiceRecordManagerErrorTooShort = 4, + KBVoiceRecordManagerErrorInterrupted = 5, +}; + +@class KBVoiceRecordManager; + +@protocol KBVoiceRecordManagerDelegate +@optional +- (void)voiceRecordManagerDidStartRecording:(KBVoiceRecordManager *)manager; +- (void)voiceRecordManager:(KBVoiceRecordManager *)manager + didFinishRecordingAtURL:(NSURL *)fileURL + duration:(NSTimeInterval)duration; +- (void)voiceRecordManagerDidCancelRecording:(KBVoiceRecordManager *)manager; +- (void)voiceRecordManagerDidRecordTooShort:(KBVoiceRecordManager *)manager; +- (void)voiceRecordManager:(KBVoiceRecordManager *)manager + didFailWithError:(NSError *)error; +@end + +/// 录音管理器(AAC/M4A,16k,mono) +@interface KBVoiceRecordManager : NSObject + +@property(nonatomic, weak) id delegate; +@property(nonatomic, assign) NSTimeInterval minRecordDuration; +@property(nonatomic, assign, readonly, getter=isRecording) BOOL recording; + +- (void)startRecording; +- (void)stopRecording; +- (void)cancelRecording; + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/AiTalk/VM/KBVoiceRecordManager.m b/keyBoard/Class/AiTalk/VM/KBVoiceRecordManager.m new file mode 100644 index 0000000..a9ded40 --- /dev/null +++ b/keyBoard/Class/AiTalk/VM/KBVoiceRecordManager.m @@ -0,0 +1,289 @@ +// +// KBVoiceRecordManager.m +// keyBoard +// +// Created by Mac on 2026/1/26. +// + +#import "KBVoiceRecordManager.h" +#import + +static NSString *const kKBVoiceRecordManagerErrorDomain = + @"KBVoiceRecordManager"; + +@interface KBVoiceRecordManager () + +@property(nonatomic, strong) AVAudioRecorder *recorder; +@property(nonatomic, strong) NSURL *currentFileURL; +@property(nonatomic, assign) BOOL recording; +@property(nonatomic, assign) BOOL stopping; +@property(nonatomic, assign) BOOL cancelled; +@property(nonatomic, assign) BOOL interrupted; +@property(nonatomic, assign) NSTimeInterval recordStartTime; + +@end + +@implementation KBVoiceRecordManager + +- (instancetype)init { + self = [super init]; + if (self) { + _minRecordDuration = 1.0; + [self setupNotifications]; + } + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +#pragma mark - Public Methods + +- (void)startRecording { + if (self.recording) { + return; + } + + AVAudioSession *session = [AVAudioSession sharedInstance]; + if (session.recordPermission == AVAudioSessionRecordPermissionUndetermined) { + __weak typeof(self) weakSelf = self; + [session requestRecordPermission:^(BOOL granted) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (granted) { + [weakSelf startRecordingInternal]; + } else { + [weakSelf reportErrorWithCode:KBVoiceRecordManagerErrorPermissionDenied + message:@"Microphone permission denied"]; + } + }); + }]; + return; + } + + if (session.recordPermission != AVAudioSessionRecordPermissionGranted) { + [self reportErrorWithCode:KBVoiceRecordManagerErrorPermissionDenied + message:@"Microphone permission denied"]; + return; + } + + [self startRecordingInternal]; +} + +- (void)stopRecording { + if (!self.recording || !self.recorder) { + return; + } + self.stopping = YES; + self.cancelled = NO; + self.interrupted = NO; + [self.recorder stop]; +} + +- (void)cancelRecording { + if (!self.recording || !self.recorder) { + return; + } + self.cancelled = YES; + self.stopping = NO; + self.interrupted = NO; + [self.recorder stop]; +} + +#pragma mark - Private Methods + +- (void)startRecordingInternal { + NSError *sessionError = nil; + if (![self configureSession:&sessionError]) { + [self reportErrorWithCode:KBVoiceRecordManagerErrorSessionFailed + message:sessionError.localizedDescription ?: @"Session error"]; + return; + } + + NSURL *fileURL = [self generateFileURL]; + if (!fileURL) { + [self reportErrorWithCode:KBVoiceRecordManagerErrorRecorderFailed + message:@"Invalid file URL"]; + return; + } + + NSDictionary *settings = @{ + AVFormatIDKey : @(kAudioFormatMPEG4AAC), + AVSampleRateKey : @(16000), + AVNumberOfChannelsKey : @(1), + AVEncoderAudioQualityKey : @(AVAudioQualityMedium), + AVEncoderBitRateKey : @(32000) + }; + + NSError *recorderError = nil; + self.recorder = [[AVAudioRecorder alloc] initWithURL:fileURL + settings:settings + error:&recorderError]; + if (recorderError || !self.recorder) { + [self reportErrorWithCode:KBVoiceRecordManagerErrorRecorderFailed + message:recorderError.localizedDescription ?: @"Recorder error"]; + return; + } + + self.currentFileURL = fileURL; + self.recorder.delegate = self; + self.recorder.meteringEnabled = NO; + self.stopping = NO; + self.cancelled = NO; + self.interrupted = NO; + + if (![self.recorder prepareToRecord] || ![self.recorder record]) { + [self reportErrorWithCode:KBVoiceRecordManagerErrorRecorderFailed + message:@"Recorder start failed"]; + return; + } + + self.recordStartTime = CACurrentMediaTime(); + self.recording = YES; + if ([self.delegate respondsToSelector:@selector(voiceRecordManagerDidStartRecording:)]) { + [self.delegate voiceRecordManagerDidStartRecording:self]; + } +} + +- (BOOL)configureSession:(NSError **)error { + AVAudioSession *session = [AVAudioSession sharedInstance]; + BOOL success = + [session setCategory:AVAudioSessionCategoryPlayAndRecord + mode:AVAudioSessionModeVoiceChat + options:(AVAudioSessionCategoryOptionDefaultToSpeaker | + AVAudioSessionCategoryOptionAllowBluetooth) + error:error]; + if (!success) { + return NO; + } + + [session setPreferredSampleRate:16000 error:nil]; + [session setPreferredIOBufferDuration:0.02 error:nil]; + if ([session respondsToSelector:@selector(setPreferredInputNumberOfChannels:error:)]) { + [session setPreferredInputNumberOfChannels:1 error:nil]; + } + + return [session setActive:YES error:error]; +} + +- (NSURL *)generateFileURL { + NSString *fileName = + [NSString stringWithFormat:@"kb_record_%@.m4a", + @((NSInteger)([[NSDate date] timeIntervalSince1970] * 1000))]; + NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName]; + return [NSURL fileURLWithPath:path]; +} + +- (void)cleanupRecorder { + self.recording = NO; + self.stopping = NO; + self.cancelled = NO; + self.interrupted = NO; + self.recordStartTime = 0; + self.recorder.delegate = nil; + self.recorder = nil; + self.currentFileURL = nil; + [[AVAudioSession sharedInstance] setActive:NO error:nil]; +} + +- (void)removeCurrentFileIfNeeded { + if (!self.currentFileURL) { + return; + } + [[NSFileManager defaultManager] removeItemAtURL:self.currentFileURL error:nil]; +} + +- (void)reportErrorWithCode:(KBVoiceRecordManagerErrorCode)code + message:(NSString *)message { + NSError *error = [NSError errorWithDomain:kKBVoiceRecordManagerErrorDomain + code:code + userInfo:@{NSLocalizedDescriptionKey : message ?: @""}]; + if ([self.delegate respondsToSelector:@selector(voiceRecordManager:didFailWithError:)]) { + [self.delegate voiceRecordManager:self didFailWithError:error]; + } + [self cleanupRecorder]; +} + +#pragma mark - Notifications + +- (void)setupNotifications { + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(handleInterruption:) + name:AVAudioSessionInterruptionNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(handleRouteChange:) + name:AVAudioSessionRouteChangeNotification + object:nil]; +} + +- (void)handleInterruption:(NSNotification *)notification { + NSDictionary *info = notification.userInfo; + AVAudioSessionInterruptionType type = + [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue]; + if (type == AVAudioSessionInterruptionTypeBegan && self.recording) { + self.interrupted = YES; + [self.recorder stop]; + } +} + +- (void)handleRouteChange:(NSNotification *)notification { + NSDictionary *info = notification.userInfo; + AVAudioSessionRouteChangeReason reason = + [info[AVAudioSessionRouteChangeReasonKey] unsignedIntegerValue]; + if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable && self.recording) { + self.interrupted = YES; + [self.recorder stop]; + } +} + +#pragma mark - AVAudioRecorderDelegate + +- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder + successfully:(BOOL)flag { + if (!self.recording) { + [self cleanupRecorder]; + return; + } + + NSTimeInterval duration = recorder.currentTime; + if (duration <= 0 && self.recordStartTime > 0) { + duration = CACurrentMediaTime() - self.recordStartTime; + } + NSURL *fileURL = self.currentFileURL; + + if (self.cancelled || !flag) { + [self removeCurrentFileIfNeeded]; + if ([self.delegate respondsToSelector:@selector(voiceRecordManagerDidCancelRecording:)]) { + [self.delegate voiceRecordManagerDidCancelRecording:self]; + } + [self cleanupRecorder]; + return; + } + + if (self.interrupted) { + [self removeCurrentFileIfNeeded]; + [self reportErrorWithCode:KBVoiceRecordManagerErrorInterrupted + message:@"Audio session interrupted"]; + return; + } + + if (duration < self.minRecordDuration) { + [self removeCurrentFileIfNeeded]; + if ([self.delegate respondsToSelector:@selector(voiceRecordManagerDidRecordTooShort:)]) { + [self.delegate voiceRecordManagerDidRecordTooShort:self]; + } + [self cleanupRecorder]; + return; + } + + if (fileURL && [self.delegate respondsToSelector:@selector(voiceRecordManager:didFinishRecordingAtURL:duration:)]) { + [self.delegate voiceRecordManager:self didFinishRecordingAtURL:fileURL duration:duration]; + } + [self cleanupRecorder]; +} + +@end diff --git a/keyBoard/Class/AiTalk/VM/KBVoiceToTextManager.h b/keyBoard/Class/AiTalk/VM/KBVoiceToTextManager.h index 50c4a55..1ceb751 100644 --- a/keyBoard/Class/AiTalk/VM/KBVoiceToTextManager.h +++ b/keyBoard/Class/AiTalk/VM/KBVoiceToTextManager.h @@ -30,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, weak) id delegate; @property(nonatomic, weak, readonly) KBVoiceInputBar *inputBar; +@property(nonatomic, assign) BOOL deepgramEnabled; - (instancetype)initWithInputBar:(KBVoiceInputBar *)inputBar; - (void)prepareConnection; diff --git a/keyBoard/Class/AiTalk/VM/KBVoiceToTextManager.m b/keyBoard/Class/AiTalk/VM/KBVoiceToTextManager.m index e25a22c..c8374cc 100644 --- a/keyBoard/Class/AiTalk/VM/KBVoiceToTextManager.m +++ b/keyBoard/Class/AiTalk/VM/KBVoiceToTextManager.m @@ -20,12 +20,26 @@ @implementation KBVoiceToTextManager +- (void)setDeepgramEnabled:(BOOL)deepgramEnabled { + if (_deepgramEnabled == deepgramEnabled) { + return; + } + _deepgramEnabled = deepgramEnabled; + if (!deepgramEnabled) { + [self.deepgramManager cancel]; + [self resetTranscript]; + } else { + [self.deepgramManager prepareConnection]; + } +} + - (instancetype)initWithInputBar:(KBVoiceInputBar *)inputBar { self = [super init]; if (self) { _inputBar = inputBar; _inputBar.delegate = self; _fullText = [[NSMutableString alloc] init]; + _deepgramEnabled = YES; [self setupDeepgram]; } return self; @@ -38,10 +52,16 @@ #pragma mark - Public Methods - (void)prepareConnection { + if (!self.deepgramEnabled) { + return; + } [self.deepgramManager prepareConnection]; } - (void)disconnect { + if (!self.deepgramEnabled) { + return; + } [self.deepgramManager disconnect]; } @@ -70,8 +90,12 @@ - (void)voiceInputBarDidBeginRecording:(KBVoiceInputBar *)inputBar { [self resetTranscript]; - inputBar.statusText = @"正在连接..."; - [self.deepgramManager start]; + if (self.deepgramEnabled) { + inputBar.statusText = @"正在连接..."; + [self.deepgramManager start]; + } else { + inputBar.statusText = @"正在录音..."; + } if ([self.delegate respondsToSelector:@selector (voiceToTextManagerDidBeginRecording:)]) { @@ -80,8 +104,12 @@ } - (void)voiceInputBarDidEndRecording:(KBVoiceInputBar *)inputBar { - inputBar.statusText = @"正在识别..."; - [self.deepgramManager stopAndFinalize]; + if (self.deepgramEnabled) { + inputBar.statusText = @"正在识别..."; + [self.deepgramManager stopAndFinalize]; + } else { + inputBar.statusText = @"录音结束"; + } if ([self.delegate respondsToSelector:@selector (voiceToTextManagerDidEndRecording:)]) { @@ -92,7 +120,9 @@ - (void)voiceInputBarDidCancelRecording:(KBVoiceInputBar *)inputBar { inputBar.statusText = @"已取消"; [self resetTranscript]; - [self.deepgramManager cancel]; + if (self.deepgramEnabled) { + [self.deepgramManager cancel]; + } if ([self.delegate respondsToSelector:@selector (voiceToTextManagerDidCancelRecording:)]) { @@ -103,10 +133,16 @@ #pragma mark - DeepgramStreamingManagerDelegate - (void)deepgramStreamingManagerDidConnect { + if (!self.deepgramEnabled) { + return; + } self.inputBar.statusText = @"正在聆听..."; } - (void)deepgramStreamingManagerDidDisconnect:(NSError *_Nullable)error { + if (!self.deepgramEnabled) { + return; + } if (!error) { return; } @@ -119,10 +155,16 @@ } - (void)deepgramStreamingManagerDidUpdateRMS:(float)rms { + if (!self.deepgramEnabled) { + return; + } [self.inputBar updateVolumeRMS:rms]; } - (void)deepgramStreamingManagerDidReceiveInterimTranscript:(NSString *)text { + if (!self.deepgramEnabled) { + return; + } NSString *displayText = text ?: @""; if (self.fullText.length > 0 && displayText.length > 0) { displayText = @@ -141,6 +183,9 @@ } - (void)deepgramStreamingManagerDidReceiveFinalTranscript:(NSString *)text { + if (!self.deepgramEnabled) { + return; + } if (text.length > 0) { if (self.fullText.length > 0) { [self.fullText appendString:@" "]; @@ -160,6 +205,9 @@ } - (void)deepgramStreamingManagerDidFail:(NSError *)error { + if (!self.deepgramEnabled) { + return; + } self.inputBar.statusText = @"识别失败"; if ([self.delegate respondsToSelector:@selector (voiceToTextManager:didFailWithError:)]) {