// // KSAccompanyWebViewController.m // KulexiuForTeacher // // Created by Kyle on 2022/3/20. // #import "KSAccompanyWebViewController.h" #import "WebViewBaseConfig.h" // 基础配置 #import "SwiftImportHeader.h" // swift 桥接 #import // audio session #import // 录音 #import // web socket #import // 视频录制 #import // 节拍器 #import // midi 播放 #import "AccompanyLoadingView.h" #define KSMidiSongFileKey (@"KSDownloadMidiSong") // 合成 #import #import "KSAccompanyDraftViewController.h" #import #import "KSLogManager.h" #import "AudioEnginePlayer.h" @interface KSAccompanyWebViewController () @property (nonatomic, strong) KSAudioSessionManager *audioSessionManager; @property (nonatomic, assign) MetronomeType beatType; @property (nonatomic, strong) MidiPlayerEngine *playerEngine; // player @property (nonatomic, assign) BOOL initEngineSuccess; // 加载引擎是否成功 @property (nonatomic, assign) float songOriginalSpeed; @property (nonatomic, assign) float currentSpeed; // 当前播放的速度 @property (nonatomic, assign) BOOL isPlaying; // 是否正在播放的状态 @property (nonatomic, strong) NSString *currentSongId; // song id @property (nonatomic, strong) NSDictionary *configEngineParm; // 初始化mid播放引擎的参数 @property (nonatomic, strong) NSMutableDictionary *metronomeParm; // 节拍器播放的参数 // 是否发送了musicXML信息 @property (nonatomic, assign) BOOL hasSendStartMessage; @property (nonatomic, strong) KSAQRecordManager *AQManager; @property (nonatomic, strong) KSWebSocketManager *socketManager; @property (nonatomic, strong) NSMutableDictionary *evaluatParm; // 录制的message @property (nonatomic, strong) NSMutableDictionary *recordParm; // 评测开始时发送recordStart消息的标识 @property (nonatomic, assign) BOOL isCompareStart; // 校音开始时发送 checkStart消息标识 @property (nonatomic, assign) BOOL isSoundCheckStart; // 自定义UI控件容器 @property (nonatomic, strong) UIView *viewContainer; @property (nonatomic, strong) KSVideoRecordManager *videoRecordManager; @property (nonatomic, strong) NSDictionary *endRecordParm; @property (nonatomic, assign) BOOL isCameraOpen; @property (nonatomic, strong) AccompanyLoadingView *loadingView; @property (nonatomic, assign) BOOL isTunerRuning; @property (nonatomic, strong) Tuner *tuner; // 延迟校准开始时发送 AdjustStart消息标识 @property (nonatomic, assign) BOOL isDelayCheckStart; // 检测延迟播放器 @property (nonatomic, strong) AudioEnginePlayer *delayCheckPlayer; // 音频播放器 @property (nonatomic, strong) AudioEnginePlayer *musicPlayer; @property (nonatomic, assign) float musicSpeed; //播放速度 // 录音开始时间 @property (nonatomic, assign) NSTimeInterval recordStartTime; // 播放开始时间 @property (nonatomic, assign) NSTimeInterval playerStartTime; // 播放延迟 @property (nonatomic, assign) NSInteger offsetTime; @property (nonatomic, assign) BOOL checkPlayerReady; @property (nonatomic, assign) BOOL musicPlayerReady; @property (nonatomic, strong) NSDictionary *playerParm; @property (nonatomic, strong) NSMutableArray *delayArray; @property (nonatomic, assign) NSInteger checkIndex; @property (nonatomic, assign) NSInteger checkPrequence; @property (nonatomic, strong) NSURL *bgAudioUrl; @property (nonatomic, strong) NSURL *recordUrl; @property (nonatomic, strong) NSString *accompanyUrl; @property (nonatomic, assign) BOOL muteAccompany; // 是否静音 @property (nonatomic, assign) NSInteger musicStartTime; // 开始播放的时间 @property (nonatomic, assign) BOOL hasRecordMusicOffset; // 是否记录了播放延迟 @end @implementation KSAccompanyWebViewController - (void)handerAudioInterruption { if (_playerEngine) { [self stopPlayAction]; } if (_musicPlayer) { [self stopMp3Player]; } } - (void)resumeAudioSession { // 恢复 if (_playerEngine) { [self.playerEngine resumeAUGraph]; } } // 打断处理 - (void)audioInterruption { if (_videoRecordManager) { [self.videoRecordManager resetSession]; } [self handerAudioInterruption]; } - (void)ignorRecordVideo { if (_videoRecordManager) { self.videoRecordManager.skipSaveRecord = YES; } } - (void)stopSession { if (_videoRecordManager) { [self.videoRecordManager stopSession]; } } - (void)resetPlayerConfig { [self freeMp3Player]; // 若之前有播放器,剔除 self.checkPrequence = 800; self.checkPlayerReady = NO; self.musicPlayerReady = NO; self.recordStartTime = 0; self.playerStartTime = 0; self.offsetTime = 0; } - (void)downloadMp3File:(NSString *)musicUrl { if (![NSString isEmptyString:musicUrl]) { NSString *fileName = [musicUrl getUrlFileName]; NSString *filePath = [self getAccompanyFilePathWithName:fileName]; NSURL *fileUrl = [[NSURL alloc] initFileURLWithPath:filePath]; if ([self checkSongHasSaveAccompanyWithSongUrl:musicUrl]) { [self configVideoRecord:fileUrl]; [self initMusicPlayer:fileUrl]; } else { MJWeakSelf; [self downloadUrl:musicUrl success:^{ [weakSelf configVideoRecord:fileUrl]; [weakSelf initMusicPlayer:fileUrl]; } faliure:^{ }]; } } else { [self configVideoRecord:nil]; } } - (void)initMusicPlayer:(NSURL *)musicUrl { [self.musicPlayer prepareNativeSongWithUrl:musicUrl]; } - (void)downloadCheckMusic:(NSString *)checkMusicUrl { if (![NSString isEmptyString:checkMusicUrl]) { NSString *fileName = [checkMusicUrl getUrlFileName]; NSString *filePath = [self getAccompanyFilePathWithName:fileName]; NSURL *fileUrl = [[NSURL alloc] initFileURLWithPath:filePath]; if ([self checkSongHasSaveAccompanyWithSongUrl:checkMusicUrl]) { [self initCheckPlayer:fileUrl]; } else { MJWeakSelf; [self downloadUrl:checkMusicUrl success:^{ [weakSelf initCheckPlayer:fileUrl]; } faliure:^{ }]; } } } - (void)initCheckPlayer:(NSURL *)checkUrl { [self.delayCheckPlayer prepareNativeSongWithUrl:checkUrl]; } - (void)initMp3Player:(NSString *)musicUrl checkUrl:(NSString *)checkUrl { [self resetPlayerConfig]; self.accompanyUrl = musicUrl; [self downloadCheckMusic:checkUrl]; } - (void)freeMp3Player { if (_delayCheckPlayer) { [self.delayCheckPlayer freePlayer]; } if (_musicPlayer) { [self.musicPlayer freePlayer]; } } - (void)stopMp3Player { if (_delayCheckPlayer) { [self.delayCheckPlayer stopPlay]; } if (_musicPlayer) { [self.musicPlayer stopPlay]; } } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. [self configAudioSession]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appEnterBackground) name:@"appEnterBackground" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appEnterForeground) name:@"appEnterForeground" object:nil]; } - (void)checkMessage:(id)message { NSDictionary *result = [message mj_JSONObject]; NSString *type = [[result ks_dictionaryValueForKey:@"header"] ks_stringValueForKey:@"type"]; NSString *commond = [[result ks_dictionaryValueForKey:@"header"] ks_stringValueForKey:@"commond"]; if ([type isEqualToString:@"DELAY_CHECK"] && [commond isEqualToString:@"recordEnd"]) { NSDictionary *body = [result ks_dictionaryValueForKey:@"body"]; NSTimeInterval micDelay = [body ks_doubleValueForKey:@"firstNoteDelayDuration"] - self.offsetTime; if (micDelay > 0) { [self.delayArray addObject:[NSNumber numberWithDouble:micDelay]]; } } } - (void)connectSocketService { MJWeakSelf; self.socketManager.didReceiveMessage = ^(id _Nonnull message) { dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf checkMessage:message]; // api NSMutableDictionary *sendMessage = [NSMutableDictionary dictionary]; [sendMessage setValue:@"sendResult" forKey:@"api"]; [sendMessage setValue:message forKey:@"content"]; // 服务返回数据传给H5 [weakSelf postMessage:sendMessage]; }); }; self.socketManager.connectionStatus = ^(BOOL isSuccess) { dispatch_async(dispatch_get_main_queue(), ^{ if (!isSuccess) { // NSLog(@"-----连接失败"); if (weakSelf.hasSendStartMessage) { if (weakSelf.AQManager.isRunning) { NSDictionary *postParm = @{@"api" : @"cancelEvaluating", @"content" : @{@"reson":@"服务异常,请重试"} }; [weakSelf postMessage:postParm]; [weakSelf stopRecordService]; weakSelf.isCompareStart = NO; weakSelf.isSoundCheckStart = NO; weakSelf.isDelayCheckStart = NO; } } else if (weakSelf.evaluatParm) { NSMutableDictionary *sendParm = [NSMutableDictionary dictionaryWithDictionary:weakSelf.evaluatParm]; NSDictionary *valueDic = [weakSelf.evaluatParm ks_dictionaryValueForKey:@"content"]; [valueDic setValue:@"服务异常,请重试" forKey:@"reson"]; [sendParm setValue:valueDic forKey:@"content"]; [weakSelf postMessage:sendParm]; weakSelf.hasSendStartMessage = YES; weakSelf.isCompareStart = NO; weakSelf.isSoundCheckStart = NO; weakSelf.isDelayCheckStart = NO; } else { // 其他断开 if (weakSelf.AQManager.isRunning) { NSDictionary *postParm = @{@"api" : @"cancelEvaluating", @"content" : @{@"reson":@"服务异常,请重试"} }; [weakSelf postMessage:postParm]; [weakSelf stopRecordService]; weakSelf.isCompareStart = NO; weakSelf.isSoundCheckStart = NO; weakSelf.isDelayCheckStart = NO; } } } else { // NSLog(@"-----连接成功"); if (weakSelf.hasSendStartMessage == NO && weakSelf.evaluatParm) { NSDictionary *content = [weakSelf.evaluatParm ks_dictionaryValueForKey:@"content"]; NSString *sendData = [weakSelf configDataCommond:@"musicXml" body:content type:@"SOUND_COMPARE"]; [weakSelf sendDataToSocketService:sendData]; [weakSelf postMessage:weakSelf.evaluatParm]; weakSelf.hasSendStartMessage = YES; } } }); }; [self.socketManager SRWebSocketOpen]; } - (void)appEnterBackground { NSDictionary *parm = @{@"api":@"suspendPlay"}; [self postMessage:parm]; [self handerAudioInterruption]; } - (void)appEnterForeground { if (self.isCameraOpen) { if ([self.videoRecordManager getSessionStatusisActive] == NO) { [self.videoRecordManager configSessiondisplayInView:self.viewContainer]; } } } - (void)initWebView { [self.scrollView removeFromSuperview]; [self.view addSubview:self.viewContainer]; [self.viewContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.top.bottom.mas_equalTo(self.view); }]; if (self.myWebView == nil) { WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; config.selectionGranularity = WKSelectionGranularityDynamic; config.allowsInlineMediaPlayback = YES; config.mediaTypesRequiringUserActionForPlayback = NO; config.processPool = [KSBaseWKWebViewController singleWkProcessPool]; config.websiteDataStore = [WKWebsiteDataStore defaultDataStore]; [self configUserAgent:config]; //自定义的WKScriptMessageHandler 是为了解决内存不释放的问题 WeakWebViewScriptMessageDelegate *weakScriptMessageDelegate = [[WeakWebViewScriptMessageDelegate alloc] initWithDelegate:self]; //这个类主要用来做native与JavaScript的交互管理 WKUserContentController * wkUController = [[WKUserContentController alloc] init]; [wkUController addScriptMessageHandler:weakScriptMessageDelegate name:SCRIPT_NAME]; config.userContentController = wkUController; WKPreferences *preferences = [WKPreferences new]; // 是否支出javaScript preferences.javaScriptEnabled = YES; //不通过用户交互,是否可以打开窗口 preferences.javaScriptCanOpenWindowsAutomatically = YES; config.preferences = preferences; self.myWebView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config]; self.myWebView.opaque = NO; self.myWebView.UIDelegate = self; self.myWebView.navigationDelegate = self; self.myWebView.scrollView.bounces = NO; self.myWebView.backgroundColor = [UIColor clearColor]; self.myWebView.scrollView.backgroundColor = [UIColor clearColor]; #ifdef DEBUG if (@available(iOS 16.4, *)) { self.myWebView.inspectable = YES; } #endif // 加载进度条和title [self.myWebView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil]; [self.myWebView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL]; [self.view addSubview:self.myWebView]; [self.myWebView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.mas_equalTo(self.view); make.top.mas_equalTo(self.view.mas_top); make.bottom.mas_equalTo(self.view.mas_bottom); }]; self.myWebView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; [self setupProgress]; [self loadRequest]; } else { [self.myWebView reload]; } } - (void)configUserAgent:(WKWebViewConfiguration *)config { NSString *oldUserAgent = config.applicationNameForUserAgent; NSString *newAgent = [NSString stringWithFormat:@"%@ %@ %@",oldUserAgent,AGENT_NAME,AGENT_DOMAIN]; config.applicationNameForUserAgent = newAgent; } - (void)configRecordManager { if (self.AQManager.isRunning) { [self.AQManager stopRecord]; } self.AQManager = [[KSAQRecordManager alloc] init]; self.AQManager.delegate = self; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self connectSocketService]; if (self.isCameraOpen) { [self.videoRecordManager configSessiondisplayInView:self.viewContainer]; } } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; if (_socketManager) { [self.socketManager SRWebSocketClose]; _socketManager = nil; } // 停止播放和录制 [self stopSession]; [self stopPlayAction]; [self stopRecordService]; [self stopTuner]; BOOL isBack = [self isViewPopDismiss]; if (isBack) { // 页面销毁才删除 if (_AQManager) { [_AQManager freeAudioQueue]; } // 如果退出评测页面 清除 playerEngine if (self.playerEngine) { [self.playerEngine cleanup]; self.playerEngine = nil; } // 返回不保存视频 [self ignorRecordVideo]; [self freeMp3Player]; [self removeTuner]; } } - (void)removeTuner { if (_tuner) { [_tuner freeTuner]; _tuner = nil; NSLog(@"--- free tuner "); } } - (void)sendDataToSocketService:(id)data { if (_socketManager) { [self.socketManager sendData:data]; } } #pragma mark --- WKScriptMessageHandler - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { if ([message.name isEqualToString:SCRIPT_NAME]) { NSDictionary *parm = [self convertJsonStringToNSDictionary:message.body]; // 回到主线程 dispatch_async(dispatch_get_main_queue(), ^{ if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"createMusicPlayer"]) { NSDictionary *content = [parm ks_dictionaryValueForKey:@"content"]; NSString *musicUrl = [content ks_stringValueForKey:@"musicSrc"]; NSString *checkUrl = [content ks_stringValueForKey:@"tuneSrc"]; self.playerParm = parm; [self initMp3Player:musicUrl checkUrl:checkUrl]; // 下载文件 [self downloadMp3File:musicUrl]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"startEvaluating"]) { // 开始评测 [self configRecordManager]; self.hasSendStartMessage = NO; [RecordCheckManager checkMicPermissionAvaiableCallback:^(PREMISSIONTYPE type) { if (type == PREMISSIONTYPE_YES) { self.evaluatParm = [NSMutableDictionary dictionaryWithDictionary:parm]; // 如果socket 连上了 if (self.socketManager.socketReadyState == SR_OPEN) { NSDictionary *content = [parm ks_dictionaryValueForKey:@"content"]; NSString *sendData = [self configDataCommond:@"musicXml" body:content type:@"SOUND_COMPARE"]; [self sendDataToSocketService:sendData]; [self postMessage:parm]; self.hasSendStartMessage = YES; } else { [self connectSocketService]; } } else { [self responseMessage:@"storageUnable" desc:@"没有麦克风访问权限" parm:parm]; [self showAlertWithMessage:@"请开启麦克风访问权限" type:CHECKDEVICETYPE_MIC]; } }]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"endEvaluating"]) {// 停止评测 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ self.musicStartTime = 0; self.evaluatParm = nil; [self stopRecordService]; [self postMessage:parm]; [self sendEndMessage]; [self stopMp3Player]; // 停止播放 }); } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"cancelEvaluating"]) { // 取消评测 self.musicStartTime = 0; self.evaluatParm = nil; [self stopRecordService]; [self postMessage:parm]; [self stopMp3Player]; // 停止播放 } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"startRecording"]) { // 开始录制 NSDictionary *content = [parm ks_dictionaryValueForKey:@"content"]; self.musicStartTime = [content ks_integerValueForKey:@"firstNoteTime"]; if ([[content allKeys] containsObject:@"accompanimentState"]) { BOOL mute = [content ks_boolValueForKey:@"accompanimentState"] == NO; self.muteAccompany = mute; } else { self.muteAccompany = NO; } if ([[content allKeys] containsObject:@"speedRate"]) { // 播放速度 self.musicSpeed = [content ks_floatValueForKey:@"speedRate"]; } else { self.musicSpeed = 1.0f; } if (self->_videoRecordManager) { [self.videoRecordManager clearVideoFile]; } [self postMessage:parm]; self.isCompareStart = YES; [self startRecordService]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"endRecording"]) { // 停止录音 self.recordParm = nil; [self stopRecordService]; [self postMessage:parm]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"pauseRecording"]) { [self puaseRecordService]; [self postMessage:parm]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"resumeRecording"]) { [self resumeRecordService]; [self postMessage:parm]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"isWiredHeadsetOn"]) { [self configAudioDeviceType:parm]; } // 发送消息给socket service else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"proxyMessage"]) { [self sendMessageToSocket:parm]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"proxyServiceMessage"]) { NSDictionary *content = [parm ks_dictionaryValueForKey:@"content"]; NSString *sendData = [content mj_JSONString]; NSLog(@"proxyServiceMessage ------- %@",sendData); [self sendDataToSocketService:sendData]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"openCamera"]) { // 开启摄像头 [RecordCheckManager checkCameraPremissionAvaiableCallback:^(PREMISSIONTYPE type) { [self afterCheckCameraCheckAlbum:type parm:parm]; }]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"closeCamera"]) { // 关闭摄像头 self.isCameraOpen = NO; if (self->_videoRecordManager) { [self.videoRecordManager removeDisplay]; } [self postMessage:parm]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"startCapture"]) { // 开始录制 [RecordCheckManager checkPhotoLibraryPremissionAvaiableCallback:^(PREMISSIONTYPE type) { if (type == PREMISSIONTYPE_YES) { [self.videoRecordManager setIgnoreAudio:YES]; self.videoRecordManager.audioUrl = self.AQManager.audioUrl; [self.videoRecordManager startRecord]; [self postMessage:parm]; } else { // [self responseMessage:@"storageUnable" desc:@"没有相册存储权限" parm:parm]; // [self showAlertWithMessage:@"开启相册存储" type:CHECKDEVICETYPE_CAMREA]; } }]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"endCapture"]) { // 结束录制 if (self->_videoRecordManager) { self.endRecordParm = parm; [self.videoRecordManager stopRecord]; } else { [self postMessage:parm]; } } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"setCaptureMode"]) { NSString *modeString = [[parm ks_dictionaryValueForKey:@"content"] ks_stringValueForKey:@"mode"]; BOOL isIgnoreAudio = [modeString isEqualToString:@"evaluating"]; [self postMessage:parm]; [self.videoRecordManager setIgnoreAudio:isIgnoreAudio]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"startSoundCheck"]) { // 开始校音 [self configRecordManager]; [self postMessage:parm]; self.isCompareStart = NO; self.isSoundCheckStart = YES; self.isDelayCheckStart = NO; [self startRecordService]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"endSoundCheck"]) { // 结束校音 self.isSoundCheckStart = NO; [self stopRecordService]; [self postMessage:parm]; } // 音视频合成 else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"openAdjustRecording"]) { KSAccompanyDraftViewController *ctrl = [[KSAccompanyDraftViewController alloc] init]; ctrl.ks_landScape = YES; if (self.bgAudioUrl == nil) { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"当前曲目无mp3伴奏"]; } else { if (self.AQManager && self.AQManager.audioUrl) { self.recordUrl = self.AQManager.audioUrl; NSDictionary *content = [parm ks_dictionaryValueForKey:@"content"]; ctrl.recordId = [content ks_stringValueForKey:@"recordId"]; ctrl.songName = [content ks_stringValueForKey:@"title"]; ctrl.coverImage = [content ks_stringValueForKey:@"coverImg"]; if ([[content allKeys] containsObject:@"speedRate"]) { ctrl.musicSpeed = [content ks_floatValueForKey:@"speedRate"]; } else { ctrl.musicSpeed = 1.0f; } MJWeakSelf; NSInteger micDelay = [UserDefaultObjectForKey(@"micDelay") integerValue]; NSInteger defaultDelay = self.offsetTime + micDelay; [ctrl configWithVideoUrl:self.videoRecordManager.videoFileURL bgAudioUrl:self.bgAudioUrl remoteBgUrl:self.accompanyUrl recordUrl:self.recordUrl offsetTime:defaultDelay mergeCallback:^(BOOL isPublished) { [weakSelf appEnterForeground]; if (isPublished) { [weakSelf musicPublishCallBack:content]; } }]; [self.navigationController pushViewController:ctrl animated:NO]; } else { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"麦克风被占用"]; } } } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"videoUpdate"]) { // 上传 NSMutableDictionary *sendParm = [NSMutableDictionary dictionaryWithDictionary:parm]; NSMutableDictionary *contentParm = [NSMutableDictionary dictionaryWithDictionary:[sendParm ks_dictionaryValueForKey:@"content"]]; if (self.videoRecordManager) { MJWeakSelf; [self.videoRecordManager saveVideoCallback:^(BOOL isSuccess, NSString * _Nullable message) { if (isSuccess) { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"已保存到相册"]; [weakSelf uploadVideoWithParm:contentParm sendParm:sendParm]; } }]; } } #pragma mark -------- 云教练原生播放对接 else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"cloudDetail"]) { // 初始化方法 /** api: 'cloudDetail', content: { midi: '', denominator: 4, numerator: 4, // xml整体原始速度 originalSpeed: 90, // 间隔(ms) interval: 50 } */ [self configAudioSession]; // 重置track num array self.configEngineParm = [parm copy]; NSDictionary *content = [parm ks_dictionaryValueForKey:@"content"]; NSString *midiUrl = [content ks_stringValueForKey:@"midi"]; NSInteger denominator = [content ks_integerValueForKey:@"denominator"]; NSInteger numerator = [content ks_integerValueForKey:@"numerator"]; NSString *beatString = [NSString stringWithFormat:@"%zd/%zd", numerator,denominator]; self.beatType = [self getBeatTypeFromString:beatString]; float originalSpeed = [content ks_floatValueForKey:@"originalSpeed"]; self.songOriginalSpeed = originalSpeed; self.currentSpeed = originalSpeed; NSInteger reportInterval = [content ks_integerValueForKey:@"interval"]; // 下载midi文件 BOOL hasSaveSong = [self checkSongHasSaveWithSongUrl:midiUrl]; NSString *fileName = [midiUrl getUrlFileName]; NSString *filePath = [self getFilePathWithName:fileName]; if (hasSaveSong) { [self configPlayerEngineWithSong:filePath reportTime:reportInterval]; } else { MJWeakSelf; [self downloadMidiFile:midiUrl success:^{ [weakSelf configPlayerEngineWithSong:filePath reportTime:reportInterval]; } faliure:^{ }]; } } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"cloudGetMediaStatus"]) { // 获取播放状态 /** api: 'cloudGetMediaStatus', content: { status: 'init' | 'play' | 'suspend' } */ NSString *engineStatus = @"init"; if (self.initEngineSuccess) { if (self.isPlaying) { engineStatus = @"play"; } else { engineStatus = @"suspend"; } } NSMutableDictionary *valueDic = [NSMutableDictionary dictionaryWithDictionary:[parm ks_dictionaryValueForKey:@"content"]]; [valueDic setValue:engineStatus forKey:@"status"]; NSMutableDictionary *sendParm = [NSMutableDictionary dictionaryWithDictionary:parm]; [sendParm setValue:valueDic forKey:@"content"]; [self postMessage:sendParm]; } // 播放、暂停、进度、播放结束、跳转指定位置 else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"cloudPlay"]) { // 播放 /** api: 'cloudPlay', content: { // 当前曲目id songID: 0, // xml整体原始速度 originalSpeed: 90, // 当前选择速度 speed: 90, // 开始时间(ms) startTime: 0 // 播放频率 hertz:440 } */ NSDictionary *content = [parm ks_dictionaryValueForKey:@"content"]; self.currentSongId = [content ks_stringValueForKey:@"songID"]; float speed = [content ks_floatValueForKey:@"speed"]; float originalSpeed = [content ks_floatValueForKey:@"originalSpeed"]; double rate = speed / originalSpeed; self.currentSpeed = speed; float hertz = [content ks_floatValueForKey:@"hertz"]; // 播放的hertz [self.playerEngine adjustPitchByHZ:hertz]; [self.playerEngine setMusicPlayerSpeed:rate]; Float64 startTime = [content ks_doubleValueForKey:@"startTime"]; NSLog(@"------%@", [content ks_stringValueForKey:@"startTime"]); [self.playerEngine setProgressTime:(startTime/1000)]; // 播放 [self playAction]; [self postMessage:parm]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"cloudSuspend"]) { // 暂停 /** api: 'cloudSuspend', content: { // 当前曲目id songID: 0, } */ [self stopPlayAction]; [self postMessage:parm]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"cloudSetCurrentTime"]) { // // 跳转指定位置 /** api: 'cloudSetCurrentTime', content: { // 当前曲目id songID: 0, // 指定位置时间(ms) currentTime: 0 } */ NSDictionary *content = [parm ks_dictionaryValueForKey:@"content"]; Float64 currentTime = [content ks_doubleValueForKey:@"currentTime"]; if (self.playerEngine) { [self.playerEngine setProgressTime:(currentTime/1000)]; } [self postMessage:parm]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"cloudChangeSpeed"]) { // 调速 范围(45-270整数) /** api: 'cloudChangeSpeed', content: { // 当前曲目id songID: 0, // 调整速度 speed: 90 // xml整体原始速度 originalSpeed: 90, } */ NSDictionary *content = [parm ks_dictionaryValueForKey:@"content"]; float speed = [content ks_floatValueForKey:@"speed"]; float originalSpeed = [content ks_floatValueForKey:@"originalSpeed"]; double rate = speed / originalSpeed; self.currentSpeed = speed; [self.playerEngine setMusicPlayerSpeed:rate]; // 回报信息 [self postMessage:parm]; } // 设置每个轨道音量 else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"cloudVolume"]) { /** api: 'cloudVolume', content: { parts: [ { name: '', volume: 90,//0-100 } ] } */ NSLog(@"-cloudVolume -----%@",parm); NSDictionary *content = [parm ks_dictionaryValueForKey:@"content"]; int instrumentId = [content ks_intValueForKey:@"activeMidiId"]; float volume = [content ks_floatValueForKey:@"activeMidiVolume"]; if (instrumentId < 0) { [self postMessage:parm]; [self.playerEngine getAllTrackVolume]; return; } [self.playerEngine volumeTrackVolumeWithInstrumentId:instrumentId volume:volume/100]; [self postMessage:parm]; [self.playerEngine getAllTrackVolume]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"cloudMetronome"]) { // 节拍器播放 self.metronomeParm = [NSMutableDictionary dictionaryWithDictionary:parm]; NSDictionary *content = [parm ks_dictionaryValueForKey:@"content"]; NSInteger repeatCount = [content ks_integerValueForKey:@"repeat"]; NSInteger denominator = [content ks_integerValueForKey:@"denominator"]; NSInteger numerator = [content ks_integerValueForKey:@"numerator"]; NSInteger supplement = [content ks_integerValueForKey:@"supplement"]; NSString *beatString = [NSString stringWithFormat:@"%zd/%zd", numerator,denominator]; self.beatType = [self getBeatTypeFromString:beatString]; [self showBeatViewRepeatCount:repeatCount supplement:supplement]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"cloudDestroy"]) { // 销毁播放器 self.initEngineSuccess = NO; // 重置track num array self.isPlaying = NO; if (self.playerEngine) { [self.playerEngine cleanup]; self.playerEngine = nil; } } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"cloudLoading"]) { // loading NSDictionary *content = [parm ks_dictionaryValueForKey:@"content"]; BOOL showLoading = [content ks_boolValueForKey:@"show"]; if (showLoading) { [self showCustomLoading]; } else { [self removeCustomLoadingView]; } } // 跟音 else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"cloudToggleFollow"]) { // 跟音 [RecordCheckManager checkMicPermissionAvaiableCallback:^(PREMISSIONTYPE type) { if (type == PREMISSIONTYPE_YES) { NSDictionary *content = [parm ks_dictionaryValueForKey:@"content"]; NSString *status = [content ks_stringValueForKey:@"state"]; if ([status isEqualToString:@"start"]) { // 开始 [self configAudioSession]; [self startTuner]; } else if ([status isEqualToString:@"end"]) { // 结束 [self stopTuner]; } [self postMessage:parm]; } else { [self responseMessage:@"storageUnable" desc:@"没有麦克风权限" parm:parm]; [self showAlertWithMessage:@"请开启麦克风访问权限" type:CHECKDEVICETYPE_MIC]; } }]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"cloudAccompanyMessage"]) { // 获取伴奏 废弃⚠️ } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"startTune"]) { // 延迟测试开始 NSDictionary *content = [parm ks_dictionaryValueForKey:@"content"]; NSInteger count = [content ks_integerValueForKey:@"count"]; if (count == 0) { [self.delayArray removeAllObjects]; self.checkIndex = 0; } self.checkIndex += 1; [self configRecordManager]; [self postMessage:parm]; self.isCompareStart = NO; self.isSoundCheckStart = NO; self.isDelayCheckStart = YES; [self startRecordService]; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"endTune"]) { // 延迟测试停止 self.isDelayCheckStart = NO; [self stopRecordService]; [self postMessage:parm]; [self stopMp3Player]; [self sendAdjustEndMessage]; // 发送结束消息 } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"finishTune"]) { // 延迟测试结束 NSDictionary *content = [parm ks_dictionaryValueForKey:@"content"]; NSInteger errorCount = 0; NSInteger totalDelay = 0; for (NSInteger index = 0 ; index < self.delayArray.count; index++) { NSInteger micDelay = [self.delayArray[index] integerValue]; if (micDelay > 10 && micDelay < 300) { totalDelay += micDelay; } else { errorCount++; } } NSMutableDictionary *contentParm = [NSMutableDictionary dictionaryWithDictionary:content]; if (errorCount > 1) { // 错误次数过多 [contentParm setValue:@(NO) forKey:@"result"]; } else { [contentParm setValue:@(YES) forKey:@"result"]; NSInteger averageDelay = totalDelay / (self.delayArray.count - errorCount); UserDefaultSet([NSNumber numberWithDouble:averageDelay], @"micDelay"); } NSMutableDictionary *sendParm = [NSMutableDictionary dictionary]; [sendParm setValue:@"finishTune" forKey:@"api"]; [sendParm setValue:contentParm forKey:@"content"]; [self postMessage:sendParm]; [self.delayArray removeAllObjects]; self.checkIndex = 0; } else if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"getDeviceDelay"]) { NSInteger micDelay = [UserDefaultObjectForKey(@"micDelay") integerValue]; NSMutableDictionary *content = [NSMutableDictionary dictionaryWithDictionary:[parm ks_dictionaryValueForKey:@"content"]]; [content setValue:[NSNumber numberWithInteger:micDelay] forKey:@"value"]; NSMutableDictionary *sendParm = [NSMutableDictionary dictionary]; [sendParm setValue:@"getDeviceDelay" forKey:@"api"]; [sendParm setValue:content forKey:@"content"]; [self postMessage:sendParm]; } else { [super handleScriptMessageSource:parm]; } }); } } - (void)afterCheckCameraCheckAlbum:(PREMISSIONTYPE)cameraType parm:(NSDictionary *)sourceParm { [RecordCheckManager checkPhotoLibraryPremissionAvaiableCallback:^(PREMISSIONTYPE type) { if (type == PREMISSIONTYPE_YES && cameraType == PREMISSIONTYPE_YES) { self.isCameraOpen = YES; [self.videoRecordManager setIgnoreAudio:YES]; [self.videoRecordManager configSessiondisplayInView:self.viewContainer]; [self postMessage:sourceParm]; } else { // NSString *content = @""; NSString *des = @"";; if (cameraType == PREMISSIONTYPE_NO && type == PREMISSIONTYPE_NO) { des = @"没有相机和相册访问权限"; content = @"请开启相机和相册访问权限"; } else if (cameraType == PREMISSIONTYPE_NO && type == PREMISSIONTYPE_YES) { des = @"没有相机访问权限"; content = @"请开启相机访问权限"; } else if (cameraType == PREMISSIONTYPE_YES && type == PREMISSIONTYPE_NO) { des = @"没有相册访问权限"; content = @"请开启相册访问权限"; } [self responseMessage:@"storageUnable" desc:des parm:sourceParm]; [self showAlertWithMessage:content type:CHECKDEVICETYPE_CAMREA]; } }]; } - (void)responseMessage:(NSString *)reson desc:(NSString *)desc parm:(NSDictionary *)parm { NSMutableDictionary *sendContent = [NSMutableDictionary dictionaryWithDictionary:[parm ks_dictionaryValueForKey:@"content"]]; [sendContent setValue:reson forKey:@"reson"]; [sendContent setValue:desc forKey:@"des"]; NSMutableDictionary *sendParm = [NSMutableDictionary dictionaryWithDictionary:parm]; [sendParm setValue:sendContent forKey:@"content"]; [self postMessage:sendParm]; } - (void)musicPublishCallBack:(NSDictionary *)content { NSMutableDictionary *parm = [NSMutableDictionary dictionary]; [parm setValue:@"hideComplexButton" forKey:@"api"]; [parm setValue:@{} forKey:@"content"]; [self postMessage:parm]; } - (void)uploadVideoWithParm:(NSMutableDictionary *)contentParm sendParm:(NSMutableDictionary *)sendParm { MJWeakSelf; [self.videoRecordManager exportRecordVideoUploadSuccess:^(NSString * _Nonnull videoFileUrl) { } failure:^(NSString * _Nonnull desc) { }]; } - (void)showAlertWithMessage:(NSString *)message type:(CHECKDEVICETYPE)deviceType { [KSPremissionAlert shareInstanceDisplayImage:deviceType message:message showInView:self.view cancel:^{ } confirm:^{ [self openSettingView]; }]; } - (void)openSettingView { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil]; } - (void)downloadUrl:(NSString *)url success:(void(^)(void))success faliure:(void(^)(void))faliure { [KSNetworkingManager downloadFileRequestWithFileUrl:url progress:^(int64_t bytesRead, int64_t totalBytes) { } success:^(NSURL * _Nonnull fileUrl) { if ([self saveAccompanyFileWithUrl:fileUrl accompanyUrl:url]) { if (success) { success(); } } } faliure:^(NSError * _Nonnull error) { if (faliure) { faliure(); } }]; } - (void)configVideoRecord:(NSURL *)path { self.videoRecordManager.bgAudioUrl = path; self.bgAudioUrl = path; } - (void)showBeatViewRepeatCount:(NSInteger)repeatCount supplement:(NSInteger)supplement { KSCloudBeatView *beatView = [KSCloudBeatView shareInstanceWithBeatType:self.beatType speed:self.currentSpeed repeatCount:repeatCount supplement:supplement]; MJWeakSelf; [beatView startPlayWithEndCallback:^(BOOL isCancle) { if (isCancle) { // 取消 [weakSelf sendEndMetronomeMessage:YES]; } else { // 播放完成 [weakSelf sendEndMetronomeMessage:NO]; } }]; [self.view addSubview:beatView]; } - (void)sendEndMetronomeMessage:(BOOL)isCancel { NSString *status = isCancel ? @"cancel" : @"finish"; NSMutableDictionary *valueDic = [NSMutableDictionary dictionaryWithDictionary:[self.metronomeParm ks_dictionaryValueForKey:@"content"]]; [valueDic setValue:status forKey:@"status"]; NSMutableDictionary *sendParm = [NSMutableDictionary dictionaryWithDictionary:self.metronomeParm]; [sendParm setValue:valueDic forKey:@"content"]; [self postMessage:sendParm]; } - (void)downloadMidiFile:(NSString *)midiUrl success:(void(^)(void))success faliure:(void(^)(void))faliure { [KSNetworkingManager downloadFileRequestWithFileUrl:midiUrl progress:^(int64_t bytesRead, int64_t totalBytes) { } success:^(NSURL * _Nonnull fileUrl) { if ([self saveMidiFileWithUrl:fileUrl midiUrl:midiUrl]) { if (success) { success(); } } } faliure:^(NSError * _Nonnull error) { if (faliure) { faliure(); } }]; } - (BOOL)saveMidiFileWithUrl:(NSURL *)fileUrl midiUrl:(NSString *)midiUrl { NSData *sourceData = [NSData dataWithContentsOfURL:fileUrl]; NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; NSString *filePath= [cachePath stringByAppendingPathComponent:@"MidiSong"]; // 先创建子目录 NSFileManager *fileManager = [NSFileManager defaultManager]; if (![fileManager fileExistsAtPath:filePath]) { [fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:nil]; }else{ NSLog(@"已创建文件夹"); } NSString *fileName = [midiUrl getUrlFileName]; NSString *tempPath = [filePath stringByAppendingPathComponent:fileName]; BOOL success = [sourceData writeToFile:tempPath atomically:NO]; return success; } - (NSString *)getFilePathWithName:(NSString *)fileName { NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; NSString *filePath= [cachePath stringByAppendingPathComponent:@"MidiSong"]; return [filePath stringByAppendingPathComponent:fileName];; } - (BOOL)checkSongHasSaveWithSongUrl:(NSString *)songUrl { BOOL hasSaveFile = NO; NSString *fileName = [songUrl getUrlFileName]; NSString *filePath = [self getFilePathWithName:fileName]; NSFileManager *fileManager = [NSFileManager defaultManager]; if ([fileManager fileExistsAtPath:filePath]) { hasSaveFile = YES; } return hasSaveFile; } - (void)sendMessageToSocket:(NSDictionary *)parm { NSString *messageHeader = @"proxyMessage"; NSString *sendMessage = [self configDataCommond:messageHeader body:[parm ks_dictionaryValueForKey:@"content"]]; [self sendDataToSocketService:sendMessage]; } - (void)configAudioDeviceType:(NSDictionary *)parm { AUDIODEVICE_TYPE type = [KSAQRecordManager queryAudioOutputDeviceType]; NSString *valueStr = @""; BOOL checkIsWired = NO; switch (type) { case AUDIODEVICE_TYPE_HEADPHONE: { valueStr = @"有线耳机"; checkIsWired = YES; } break; case AUDIODEVICE_TYPE_BLUETOOTH: { valueStr = @"蓝牙耳机"; checkIsWired = YES; } break; case AUDIODEVICE_TYPE_NONE: { valueStr = @""; checkIsWired = NO; } break; default: break; } NSMutableDictionary *valueDic = [NSMutableDictionary dictionaryWithDictionary:[parm ks_dictionaryValueForKey:@"content"]]; [valueDic setValue:valueStr forKey:@"type"]; [valueDic setValue:[NSNumber numberWithBool:checkIsWired] forKey:@"checkIsWired"]; NSMutableDictionary *sendParm = [NSMutableDictionary dictionaryWithDictionary:parm]; [sendParm setValue:valueDic forKey:@"content"]; [self postMessage:sendParm]; } - (void)startRecordService { if (self.AQManager.isRunning) { [self.AQManager stopRecord]; } [self.AQManager startRecord]; } - (void)stopRecordService { if (self.AQManager.isRunning) { [self.AQManager stopRecord]; } } - (void)puaseRecordService { if (self.AQManager.isRunning) { [self.AQManager pauserRecord]; } } - (void)resumeRecordService { [self.AQManager resumeRecord]; } - (void)sendEndMessage { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 上传停止的信息 发送给服务端 NSString *endMessage = @"recordEnd"; NSString *endData = [self configDataCommond:endMessage body:nil type:@"SOUND_COMPARE"]; [self sendDataToSocketService:endData]; self.isCompareStart = NO; NSLog(@"---- send end message"); }); } - (void)sendAdjustEndMessage { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 上传停止的信息 发送给服务端 NSString *endMessage = @"recordEnd"; NSString *endData = [self configDataCommond:endMessage body:nil type:@"DELAY_CHECK"]; [self sendDataToSocketService:endData]; self.isDelayCheckStart = NO; NSLog(@"---- send adjust end message"); }); } - (void)sendOffsetTimeToService { // 上传停止的信息 发送给服务端 NSString *offsetMessage = @"audioPlayStart"; NSTimeInterval micDelay = [UserDefault(@"micDelay") doubleValue]; NSDictionary *dic = @{@"offsetTime" : [NSNumber numberWithInteger:self.offsetTime], @"micDelay": [NSNumber numberWithInteger:micDelay]}; NSString *endData = [self configDataCommond:offsetMessage body:dic type:@"SOUND_COMPARE"]; NSLog(@"------ %@", endData); [self sendDataToSocketService:endData]; self.isCompareStart = NO; } #pragma mark-------- KSAQRecordManagerDelegate - (void)recordInterruption { NSDictionary *postParm = @{@"api" : @"cancelEvaluating", @"content" : @{@"reson":@"录制错误,请重试"} }; [self postMessage:postParm]; } - (void)audioRouteChange:(AUDIODEVICE_TYPE)type { NSString *valueStr = @""; BOOL checkIsWired = NO; switch (type) { case AUDIODEVICE_TYPE_HEADPHONE: { valueStr = @"有线耳机"; checkIsWired = YES; } break; case AUDIODEVICE_TYPE_BLUETOOTH: { valueStr = @"蓝牙耳机"; checkIsWired = YES; } break; case AUDIODEVICE_TYPE_NONE: { valueStr = @""; checkIsWired = NO; } break; default: break; } NSDictionary *postParm = @{@"api" : @"listenerWiredStatus", @"content" : @{@"type":valueStr, @"checkIsWired":[NSNumber numberWithBool:checkIsWired] } }; [self postMessage:postParm]; } #pragma mark ------- 评测app播放 - (void)recordDidStart:(NSTimeInterval)time { // ms self.hasRecordMusicOffset = NO; self.recordStartTime = time; if (self.isDelayCheckStart) { NSLog(@"---- delay - record did start %f", time); // 播放音频 // 播放校音音频 dispatch_main_sync_safe(^{ [self.delayCheckPlayer seekToTimePlay:0]; }); } else if (self.isCompareStart) { NSLog(@"---- compare - record did start %f", time); // 播放伴奏 dispatch_main_sync_safe(^{ self.musicPlayer.isMute = self.muteAccompany; self.musicPlayer.rate = self.musicSpeed; // 进度跳转 [self.musicPlayer seekToTimePlay:self.musicStartTime]; }); } } - (void)audioRecord:(KSAQRecordManager *)audioRecord didRecordAudioData:(void *)data length:(UInt32)length { if (self.socketManager.socketReadyState != SR_OPEN) { return; } NSData *pushData = [[NSData alloc] initWithBytes:data length:length]; if (self.isCompareStart) { // 发送评测开始消息 dispatch_async(dispatch_get_main_queue(), ^{ NSDate *date = [NSDate date]; NSTimeInterval inteveral = [date timeIntervalSince1970]; double beginTime = inteveral - audioRecord.sampleTime; NSDictionary *parm = @{ @"api" : @"recordStartTime", @"content" : @{@"inteveral" : [NSNumber numberWithDouble:beginTime]} }; [self postMessage:parm]; }); NSLog(@"--------- send start message"); _isCompareStart = NO; NSString *startMessage = @"recordStart"; NSString *startString = [self configDataCommond:startMessage body:nil type:@"SOUND_COMPARE"]; [self sendDataToSocketService:startString]; } else if (self.isSoundCheckStart) { // 校音开始 NSLog(@"--------- send check start message"); _isSoundCheckStart = NO; NSString *checkStartMessage = @"start"; NSString *startString = [self configDataCommond:checkStartMessage body:nil type:@"PITCH_DETECTION"]; [self sendDataToSocketService:startString]; } else if (self.isDelayCheckStart) { NSLog(@"--------- send delay check start message"); _isDelayCheckStart = NO; NSString *checkStartMessage = @"recordStart"; NSInteger frequence = self.checkPrequence; NSDictionary *parm = @{@"HZ" : @(frequence)}; NSString *startString = [self configDataCommond:checkStartMessage body:parm type:@"DELAY_CHECK"]; [self sendDataToSocketService:startString]; } // NSLog(@"--------- send audio data length %d", length); [self sendDataToSocketService:pushData]; } - (NSString *)configDataCommond:(NSString *)commond body:(id)bodyMessage type:(NSString *)dataType { NSMutableDictionary *parm = [NSMutableDictionary dictionary]; if (bodyMessage) { [parm setValue:bodyMessage forKey:@"body"]; } NSMutableDictionary *headerParm = [NSMutableDictionary dictionary]; if ([NSString isEmptyString:commond]) { [headerParm setValue:@"" forKey:@"commond"]; } else { [headerParm setValue:commond forKey:@"commond"]; } if (![NSString isEmptyString:dataType]) { [headerParm setValue:dataType forKey:@"type"]; } [headerParm setValue:@(200) forKey:@"status"]; [parm setValue:headerParm forKey:@"header"]; return [parm mj_JSONString]; } - (NSString *)configDataCommond:(NSString *)commond body:(id)bodyMessage { NSMutableDictionary *parm = [NSMutableDictionary dictionary]; if (bodyMessage) { [parm setValue:bodyMessage forKey:@"body"]; } if ([NSString isEmptyString:commond]) { [parm setValue:@{@"commond":@""} forKey:@"header"]; } else { [parm setValue:@{@"commond":commond} forKey:@"header"]; } return [parm mj_JSONString]; } - (KSWebSocketManager *)socketManager { if (!_socketManager) { _socketManager = [[KSWebSocketManager alloc] initWithUrl:SOCKET_URL userId:UserDefault(UIDKey) tokenType:UserDefault(TokenKey) token:UserDefault(TokenKey)]; } return _socketManager; } #pragma mark --- lazying - (UIView *)viewContainer { if (!_viewContainer) { _viewContainer = [[UIView alloc] init]; } return _viewContainer; } - (KSVideoRecordManager *)videoRecordManager { if (!_videoRecordManager) { MJWeakSelf; _videoRecordManager = [[KSVideoRecordManager alloc] initSessionRecordCallback:^(BOOL isSuccess, NSString * _Nullable message) { if (isSuccess) { [weakSelf showSuccessMessage:message]; } else { if (![NSString isEmptyString:message]) { [LOADING_MANAGER MBShowAUTOHidingInWindow:message]; } } }]; [_videoRecordManager errorMessageCallback:^(NSDictionary * _Nonnull errorParm) { [weakSelf uploadVideoRecordErrorMessage:errorParm]; }]; } return _videoRecordManager; } - (void)uploadVideoRecordErrorMessage:(NSDictionary *)errorParm { NSMutableDictionary *parm = [NSMutableDictionary dictionaryWithDictionary:errorParm]; [parm setValue:UserDefault(UIDKey) forKey:@"userId"]; [parm setValue:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] forKey:@"version"]; NSString *content = [parm mj_JSONString]; NSMutableDictionary *submitParm = [KSLogManager generateLogMessageWithContent:content type:@"ERROR"]; NSMutableArray *uploadArray = [NSMutableArray arrayWithObject:submitParm]; [KSNetworkingManager sysExceptionLogUpdate:KS_POST token:UserDefault(TokenKey) logArray:uploadArray success:^(NSDictionary * _Nonnull dic) { if ([dic ks_integerValueForKey:@"code"] == 200) { } } faliure:^(NSError * _Nonnull error) { }]; } - (void)showSuccessMessage:(NSString *)message { if (![NSString isEmptyString:message]) { [LOADING_MANAGER MBShowAUTOHidingInWindow:message]; } // 成功 if (self.endRecordParm) { [self postMessage:self.endRecordParm]; self.endRecordParm = nil; } } - (MetronomeType)getBeatTypeFromString:(NSString *)typeString { if ([typeString isEqualToString:@"1/4"]) { return MetronomeType1V4; } else if ([typeString isEqualToString:@"2/4"]) { return MetronomeType2V4; } else if ([typeString isEqualToString:@"3/4"]) { return MetronomeType3V4; } else if ([typeString isEqualToString:@"4/4"]) { return MetronomeType4V4; } else if ([typeString isEqualToString:@"3/8"]) { return MetronomeType3V8; } else if ([typeString isEqualToString:@"6/8"]) { return MetronomeType6V8; } else { return MetronomeType4V4; } } #pragma mark ------- midi 播放相关 - (void)configPlayerEngineWithSong:(NSString *)songPath reportTime:(NSInteger)reportTime { self.initEngineSuccess = NO; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ self.playerEngine = [[MidiPlayerEngine alloc] init]; self.playerEngine.reportTime = reportTime; self.playerEngine.delegate = self; [self.playerEngine configSoundFilePath:[[NSBundle mainBundle] pathForResource:@"synthgms" ofType:@"sf2"]]; [self.playerEngine loadMIDIFileWithString:songPath]; }); } - (void)configAudioSession { self.audioSessionManager = [[KSAudioSessionManager alloc] init]; self.audioSessionManager.delegate = self; [self.audioSessionManager configAudioSession:AUDIOCONFIG_PLAYANDRECORD]; } #pragma mark ---- PlayerEngineDelegate /** 进度更新 api: 'cloudTimeUpdae', content: { // 当前曲目id songID: 0, // 当前位置时间(ms) currentTime: 0 } */ /** 播放结束事件 api: 'cloudplayed', content: { // 当前曲目id songID: 0, } */ - (void)ProgressUpdated:(float)progress currentPlayTime:(MusicTimeStamp)currentPlayTime currentTime:(NSTimeInterval)currentTime { if (self.isPlaying == NO) { return; } // 回调 NSMutableDictionary *sendParm = [NSMutableDictionary dictionary]; [sendParm setValue:@"cloudTimeUpdae" forKey:@"api"]; NSMutableDictionary *content = [NSMutableDictionary dictionary]; [content setValue:self.currentSongId forKey:@"songID"]; [content setValue:@(currentPlayTime*1000) forKey:@"currentTime"]; [sendParm setValue:content forKey:@"content"]; [self postMessage:sendParm]; } - (void)playEnd { [self stopPlayAction]; NSMutableDictionary *sendParm = [NSMutableDictionary dictionary]; [sendParm setValue:@"cloudplayed" forKey:@"api"]; NSMutableDictionary *content = [NSMutableDictionary dictionary]; [content setValue:self.currentSongId forKey:@"songID"]; [sendParm setValue:content forKey:@"content"]; [self postMessage:sendParm]; } - (void)initPlayerEngineSuccess:(float)totalTime { self.initEngineSuccess = YES; if (self.configEngineParm) { NSMutableDictionary *sendParm = [NSMutableDictionary dictionaryWithDictionary:self.configEngineParm]; NSMutableDictionary *content = [NSMutableDictionary dictionaryWithDictionary:[sendParm ks_dictionaryValueForKey:@"content"]]; [content setValue:@(totalTime*1000) forKey:@"midiDuration"]; [sendParm setValue:content forKey:@"content"]; [self postMessage:sendParm]; self.configEngineParm = nil; } } // 总时长 - (void)GetMusicTotalTime:(float)time { } #pragma mark ----- 播放控制 - (void)playAction { if (self.playerEngine) { self.isPlaying = YES; [self.playerEngine playMIDIFile]; } } - (void)stopPlayAction { if (self.playerEngine) { self.isPlaying = NO; if ([self.playerEngine isPlayingFile]) { [self.playerEngine stopPlayingMIDIFile]; } } } #pragma mark ----- 小酷AI loading - (AccompanyLoadingView *)loadingView { if (!_loadingView) { _loadingView = [AccompanyLoadingView shareInstance]; MJWeakSelf; [_loadingView loadingCallback:^{ [weakSelf backAction]; }]; } return _loadingView; } - (void)showCustomLoading { if ([self.view.subviews containsObject:self.loadingView]) { return; } [self.view addSubview:self.loadingView]; [self.loadingView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.top.right.bottom.mas_equalTo(self.view); }]; [self.view bringSubviewToFront:self.loadingView]; [self.loadingView showLoading]; } - (void)removeCustomLoadingView { [self.loadingView stopLoading]; } #pragma mark ----- 跟音模块 - (void)startTuner { @try { if (self.isTunerRuning == NO) { self.isTunerRuning = YES; [self.tuner start]; } } @catch (NSException *exception) { NSLog(@"----- exception --- %@", exception); } @finally { } } - (void)stopTuner { if (self.isTunerRuning) { self.isTunerRuning = NO; [self.tuner stop]; } } - (Tuner *)tuner { if (!_tuner) { _tuner = [[Tuner alloc] initWithThreshold:0 smoothing:0.25]; _tuner.delegate = self; } return _tuner; } - (void)tunerDidUpdate:(Tuner *)tuner output:(TunerOutput *)output { if (output.amplitude < 0.01) { } else { // 回调频率 NSDictionary *parm = @{ @"api" : @"cloudFollowTime", @"content" : @{@"frequency" : [NSNumber numberWithDouble:output.frequency]} }; [self postMessage:parm]; } NSLog(@"-------- %@%zd --- distance :%f frequence : %f" , output.pitch, output.octave, output.distance, output.frequency); } #pragma mark ---- 保存伴奏 - (BOOL)saveAccompanyFileWithUrl:(NSURL *)fileUrl accompanyUrl:(NSString *)accompanyUrl { NSData *sourceData = [NSData dataWithContentsOfURL:fileUrl]; NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; NSString *filePath= [cachePath stringByAppendingPathComponent:@"AccompanySong"]; // 先创建子目录 NSFileManager *fileManager = [NSFileManager defaultManager]; if (![fileManager fileExistsAtPath:filePath]) { [fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:nil]; }else{ NSLog(@"已创建文件夹"); } NSString *fileName = [accompanyUrl getUrlFileName]; NSString *tempPath = [filePath stringByAppendingPathComponent:fileName]; BOOL success = [sourceData writeToFile:tempPath atomically:NO]; return success; } - (NSString *)getAccompanyFilePathWithName:(NSString *)fileName { NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; NSString *filePath= [cachePath stringByAppendingPathComponent:@"AccompanySong"]; return [filePath stringByAppendingPathComponent:fileName];; } - (BOOL)checkSongHasSaveAccompanyWithSongUrl:(NSString *)songUrl { BOOL hasSaveFile = NO; NSString *fileName = [songUrl getUrlFileName]; NSString *filePath = [self getAccompanyFilePathWithName:fileName]; NSFileManager *fileManager = [NSFileManager defaultManager]; if ([fileManager fileExistsAtPath:filePath]) { hasSaveFile = YES; } return hasSaveFile; } #pragma mark ------- Audio Engine player - (AudioEnginePlayer *)delayCheckPlayer { if (!_delayCheckPlayer) { _delayCheckPlayer = [[AudioEnginePlayer alloc] init]; _delayCheckPlayer.delegate = self; } return _delayCheckPlayer; } - (AudioEnginePlayer *)musicPlayer { if (!_musicPlayer) { _musicPlayer = [[AudioEnginePlayer alloc] init]; _musicPlayer.delegate = self; } return _musicPlayer; } #pragma mark ---- Audio Engine player delegate - (void)enginePlayerIsReadyPlay:(AudioEnginePlayer *)player { if (player == self.delayCheckPlayer) { self.checkPlayerReady = YES; } else if (player == self.musicPlayer) { self.musicPlayerReady = YES; } // 如果都准备好 if (self.musicPlayerReady && self.checkPlayerReady) { [self sendPlayerReadyMsg]; } } // 播放进度 - (void)updatePlayProgress:(NSInteger)playTime andTotalTime:(NSInteger)totalTime andProgress:(CGFloat)progress currentInterval:(NSTimeInterval)currentInterval inPlayer:(AudioEnginePlayer *)player { if (player == self.delayCheckPlayer) { if (playTime >= 300 && self.recordStartTime > 0 && self.hasRecordMusicOffset == NO) { self.hasRecordMusicOffset = YES; NSLog(@" --- check player start play time %f", currentInterval - playTime); self.playerStartTime = currentInterval - playTime; self.offsetTime = self.playerStartTime - self.recordStartTime; NSLog(@"--------- check player offset time -- %zd", self.offsetTime); } } // 如果未记录延迟 if (playTime >= (self.musicStartTime + 300) && player == self.musicPlayer && self.hasRecordMusicOffset == NO) { if (self.recordStartTime > 0) { self.hasRecordMusicOffset = YES; NSInteger newPlayTime = (playTime - self.musicStartTime) / player.rate; // 选段 NSLog(@" --- music player start play time %f", currentInterval - newPlayTime); self.playerStartTime = currentInterval - newPlayTime; self.offsetTime = self.playerStartTime - self.recordStartTime; NSLog(@"--------- music play offset time -- %zd", self.offsetTime); [self sendOffsetTimeToService]; } NSLog(@"------- record start time %f", self.recordStartTime); } // 回调进度 if (player == self.musicPlayer) { // NSLog(@"------ music play progress - %f", progress); // 回调进度 NSDictionary *parm = @{ @"api" : @"playProgress", @"content" : @{@"currentTime" : [NSNumber numberWithInteger:playTime], @"totalDuration" : [NSNumber numberWithInteger:totalTime], } }; NSLog(@" -----music play progress %@---- ", parm); [self postMessage:parm]; } } // 错误 - (void)enginePlayerDidError:(AudioEnginePlayer *)player error:(NSError *)error { [self stopRecordService]; [self stopMp3Player]; // 停止播放 // 播放出现问题 NSDictionary *postParm = @{@"api" : @"cancelEvaluating", @"content" : @{@"reson":@"播放已停止"} }; [self postMessage:postParm]; if (error) { NSLog(@"-- error desc - %@", error.description); NSMutableDictionary *parm = [NSMutableDictionary dictionary]; [parm setValue:UserDefault(UIDKey) forKey:@"userId"]; [parm setValue:@"KSCloudWebViewController_MP3Player" forKey:@"Location"]; [parm setValue:@(error.code) forKey:@"errorCode"]; [parm setValue:error.description forKey:@"errorDesc"]; [parm setValue:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] forKey:@"version"]; NSString *content = [parm mj_JSONString]; NSMutableDictionary *submitParm = [KSLogManager generateLogMessageWithContent:content type:@"ERROR"]; NSMutableArray *uploadArray = [NSMutableArray arrayWithObject:submitParm]; [KSNetworkingManager sysExceptionLogUpdate:KS_POST token:UserDefault(TokenKey) logArray:uploadArray success:^(NSDictionary * _Nonnull dic) { if ([dic ks_integerValueForKey:@"code"] == 200) { } } faliure:^(NSError * _Nonnull error) { }]; } } - (void)sendPlayerReadyMsg { if (self.playerParm) { [self postMessage:self.playerParm]; } } - (NSMutableArray *)delayArray { if (!_delayArray) { _delayArray = [NSMutableArray array]; } return _delayArray; } /* #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */ @end