NYVoiceManager.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. //
  2. // NYVoiceManager.m
  3. // jiaPei
  4. //
  5. // Created by Ning.ge on 2023/7/26.
  6. // Copyright © 2023 JCZ. All rights reserved.
  7. //
  8. #import "NYVoiceManager.h"
  9. #import "payRequsestHandler.h"
  10. static NYVoiceManager *vomanger = nil;
  11. static dispatch_once_t onceToken;
  12. @interface NYVoiceManager ()<QCloudTTSEngineDelegate,QCloudPlayerDelegate>
  13. {
  14. NSMutableArray* _textArr;
  15. }
  16. //SDK 内置播放器
  17. @property(strong) QCloudMediaPlayer *player;
  18. // demo 层播放器,如SDK播放器不能满足需求 可以自定义播放器 如下只是一个例子
  19. //@property(strong) MediaPlayerDemo *player;
  20. @property (nonatomic,assign)float onlineSpeed;
  21. @property (nonatomic,assign)int onlineVoiceType;
  22. @property (nonatomic,assign)int onlineLanguage;
  23. @property (nonatomic,assign)float onlineVolume;
  24. @property (nonatomic,assign)float offlineSpeed;
  25. @property (nonatomic,copy)NSString *offlineVoiceType;
  26. @property (nonatomic,assign)int offlineLanguage;
  27. @property (nonatomic,assign)float offlineVolume;
  28. @property (nonatomic,assign)int responseTimeout;
  29. @property (nonatomic,assign)int requestTimeout;
  30. @property (nonatomic,assign)int checkTimeout;
  31. @property (nonatomic, copy) NSString* resource_dir;
  32. @property (nonatomic,assign)int cout; //统计合成句子数,当作utteranceId用于标记句子用
  33. @end
  34. @implementation NYVoiceManager
  35. #pragma mark - SystemMethod
  36. + (NYVoiceManager *)sharedManager {
  37. dispatch_once(&onceToken, ^{
  38. vomanger = [[self alloc] init];
  39. });
  40. return vomanger;
  41. }
  42. - (instancetype)init {
  43. self = [super init];
  44. if (self) {
  45. [self initBaseData];
  46. }
  47. return self;
  48. }
  49. - (void)dealloc {
  50. }
  51. #pragma mark - PublicMethods
  52. // 配置
  53. - (void)ny_configureTTSVoiceDK {
  54. QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
  55. [tts cancel];
  56. [_player StopPlay];
  57. [self setOnlineParam:tts];//在线
  58. }
  59. -(void)setOnlineParam:(QCloudTTSEngine*)ttsEngine {
  60. //设置合成声音速度
  61. [ttsEngine setOnlineVoiceSpeed:_onlineSpeed];
  62. //设置合成声音类型
  63. [ttsEngine setOnlineVoiceType:_onlineVoiceType];
  64. //设置合成语种
  65. [ttsEngine setOnlineVoiceLanguage:_onlineLanguage];
  66. //设置合成声音大小默认值为0
  67. [ttsEngine setOnlineVoiceVolume:_onlineVolume];
  68. //设置响应超时时间
  69. [ttsEngine setTimeoutIntervalForResource:_responseTimeout];
  70. //设置请求超时时间
  71. [ttsEngine setTimeoutIntervalForRequest:_requestTimeout];
  72. //设置检测网络间隔时间
  73. [ttsEngine setCheckNetworkIntervalTime:_checkTimeout];
  74. }
  75. - (void)setOfflineParam:(QCloudTTSEngine*)ttsEngine{
  76. [ttsEngine setOfflineVoiceType:_offlineVoiceType];
  77. [ttsEngine setOfflineVoiceSpeed:_offlineSpeed];
  78. [ttsEngine setOfflineVoiceVolume:_offlineVolume];
  79. }
  80. //开播合成播放
  81. - (void)startPayVoiceActionText:(NSString *)text completedBlock:(nonnull NYVoicePayCompletedBlock)block {
  82. _paytext = text;
  83. self.payCompletedBlock = block;
  84. QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
  85. [self setOnlineParam:tts];
  86. //设置ProjectId 不设置默认使用0,说明:项目功能用于按项目管理云资源,可以对云资源进行分项目管理,详情见 https://console.cloud.tencent.com/project
  87. //[tts setOnlineProjectId:0];
  88. [self stopTTSBtnAction:nil];
  89. _textArr = [self breakIntoSentencesFromString:text];
  90. for (int i = 0; i < 10 - [_player getAudioQueueSize]; i++) {
  91. if (_textArr.count > 0) {
  92. [tts synthesize:_textArr[0] UtteranceId:[NSString stringWithFormat:@"%d",_cout++]];
  93. [_textArr removeObjectAtIndex:0];
  94. }
  95. }
  96. }
  97. //暂停合成播放
  98. - (void)pausePayVoiceAction {
  99. [self pauseTTSBtnAction:nil];
  100. }
  101. //恢复合成播放
  102. - (void)resumePayVoiceAction {
  103. [self resumeTTSBtnAction:nil];
  104. }
  105. //停止合成播放
  106. - (void)stopPayVoiceAction {
  107. [self stopTTSBtnAction:nil];
  108. }
  109. - (IBAction)stopTTSBtnAction:(id)sender {
  110. QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
  111. [tts cancel];
  112. [_player StopPlay];
  113. [_textArr removeAllObjects];
  114. }
  115. - (IBAction)pauseTTSBtnAction:(id)sender {
  116. [_player PausePlay];
  117. }
  118. - (IBAction)resumeTTSBtnAction:(id)sender {
  119. [_player ResumePlay];
  120. }
  121. #pragma mark - PrivateMethods
  122. - (void)initBaseData {
  123. _cout = 0;
  124. _textArr = [NSMutableArray array];
  125. QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
  126. //如果使用STS临时证书鉴权时需要设置Token
  127. [tts setOnlineAuthParam:QDApp_Id SecretId:QDSecret_ID SecretKey:QDSecret_Key Token:nil];
  128. [tts engineInit:0 Delegate:self];
  129. [tts setEnableDebugLog:YES];
  130. //SDK 内置播放器
  131. _player = [[QCloudMediaPlayer alloc]init];
  132. // demo 层播放器,如SDK播放器不能满足需求 可以自定义播放器 如下只是一个例子 源码在MediaPlayerDemo.m文件 可自行修改
  133. //_player = [[MediaPlayerDemo alloc]init];
  134. _player.playerDelegate = self;
  135. //离线参数
  136. //在线参数
  137. _onlineSpeed = 0;
  138. _onlineVoiceType = 1001;
  139. //主语言类型:1-中文(默认)2-英文
  140. _onlineLanguage = 1;
  141. _onlineVolume = 0;
  142. //连接超时默认15000ms(15s) 范围[500,30000] 单位ms , Mix模式下建议调小此值,以获得更好的体验
  143. _responseTimeout = 15*1000;
  144. //读取超时超时默认30000ms(30s) 范围[2200,60000] 单位ms, Mix模式下建议调小此值,以获得更好的体验
  145. _requestTimeout = 30 * 1000;
  146. //离线参数,如果您下载的是在线版SDK,以下参数不需要设置,请忽略
  147. _offlineSpeed = 1.0;//离线语速[0.5,2.0]
  148. //voiceType 离线音色名称,名称配置位于音色资源目录tts_resource\voices\config.json 中,可自行增删音色
  149. _offlineVoiceType = @"pb";
  150. _offlineVolume = 1.0;//离线音量 > 0
  151. /*_checkTimeout 大于等于0 单位s, 等于0时持续检测,直到成功
  152. Mix模式下,已经连接网络,但出现网络错误后的检测间隔时间,用于从离线模式自动切换回在线模式,默认值5分钟
  153. 注意:每次检测时将使用所入参的一句文本请求服务器,如果后端合成成功将会额外消耗该句字数的合成额度
  154. */
  155. _checkTimeout = 300;
  156. @try {
  157. NSDictionary *dict = @{
  158. @"onlineSpeed":@"0",//设置合成声音速度0默认 1正常 1.2倍
  159. @"onlineVoiceType":@"1001",//设置合成声音类型
  160. @"onlineLanguage":@"1",//设置合成语种-1中文 2英文
  161. @"onlineVolume":@"0",//设置合成声音大小默认值为0
  162. };
  163. if(dict){ //读取服务器配置
  164. _onlineSpeed = [dict[@"onlineSpeed"] floatValue];
  165. _onlineVoiceType = [dict[@"onlineVoiceType"] intValue];
  166. _onlineLanguage = [dict[@"onlineLanguage"] intValue];
  167. _onlineVolume = [dict[@"onlineVolume"] floatValue];
  168. }
  169. } @catch (NSException *exception) {
  170. NSLog(@"%@",exception.description);
  171. }
  172. }
  173. #pragma mark -- QCloudTTSEngineDelegate
  174. //合成结果返回
  175. //---------QCloudTTSEngineDelegate---------
  176. /// 合成回调
  177. /// @param data 语音数据
  178. /// @param utteranceId 语句id
  179. /// @param text 文本
  180. /// @param type 引擎类型 0:在线 1:离线
  181. ///
  182. -(void) onSynthesizeData:(NSData *_Nullable)data UtteranceId:(NSString *_Nullable)utteranceId Text:(NSString *_Nullable)text EngineType:(NSInteger)type RequestId:(NSString*)requestId{
  183. NSLog(@"text====%@,utteranceId ==%@,type==%ld,requestId=%@",text,utteranceId,(long)type,requestId);
  184. [_player enqueueWithData:data Text:text UtteranceId:utteranceId];
  185. // //通过保存音频的URL播放
  186. // //NSString *str = [self filePathWithName:@"tmp.mp3"];
  187. // //NSURL * url = [NSURL URLWithString:str];
  188. // //[_player enqueueWithFile:url Text:text UtteranceId:utteranceId];
  189. //
  190. //
  191. // _messageTextView.text = [NSString stringWithFormat:@"%@\n text=%@\n utteranceId=%@ requestId=%@",_messageTextView.text,text,utteranceId,requestId];
  192. // [self scrollToBottom];
  193. //
  194. // NSLog(@"onSynthesizeData %@",@(data.length));
  195. }
  196. /// 错误回调
  197. /// @param error 错误信息
  198. /// @param utteranceId utteranceId
  199. /// @param text text
  200. -(void) onError:(TtsError *_Nullable)error UtteranceId:(NSString *_Nullable)utteranceId Text:(NSString *_Nullable)text{
  201. // if (error.serviceError != nil && _ttsMode == TTS_MODE_MIX) {
  202. // //实际业务上判断一下,如果是混合模式下返回在线合成的后端错误码,应当忽略可不做处理
  203. // //SDK内会继续调用离线合成继续工作,如果没有日志需求,这里直接return忽略即可
  204. // //return;
  205. // }else{
  206. // QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
  207. // [tts cancel];
  208. // }
  209. //
  210. // NSString* msg = [NSString stringWithFormat:@"%@\n error=%@\n error.code=%@\n error.message=%@\n response = %@\n utteranceId=%@",_messageTextView.text,error.error,@(error.err_code),error.msg,error.serviceError.respose, utteranceId];
  211. // _messageTextView.text = msg;
  212. // [self scrollToBottom];
  213. // NSLog(@"onError %@",msg);
  214. }
  215. //离线或者混合模式下,调用初始化后必须收到此回调,并且OfflineAuthInfo.err_code为0时,才可以调用合成接口,如果err_code不为0,请检查参数或者网络,重新init
  216. - (void)onOfflineAuthInfo:(QCloudOfflineAuthInfo * _Nonnull)OfflineAuthInfo {
  217. // NSString *msg = [NSString stringWithFormat:@"OfflineAuthInfo:err_code=%@,err_msg=%@\n,deviceId=%@\n,expireTime=%@\n,respose=%@\n,authVoiceList=%@",@(OfflineAuthInfo.err_code),OfflineAuthInfo.err_msg,OfflineAuthInfo.deviceId,OfflineAuthInfo.expireTime,OfflineAuthInfo.respose,OfflineAuthInfo.voiceAuthList];
  218. //
  219. // NSLog(@"%@",msg);
  220. // _messageTextView.text = msg;
  221. // [self scrollToBottom];
  222. }
  223. //---------QCloudPlayerDelegate---------
  224. //播放开始
  225. -(void) onTTSPlayStart{
  226. // _messageTextView.text = [NSString stringWithFormat:@"%@\n onTTSPlayStart",_messageTextView.text];
  227. // [self scrollToBottom];
  228. NSLog(@"onTTSPlayStart");
  229. self.state = NYVoiceType_Start;
  230. if(self.payCompletedBlock){
  231. self.payCompletedBlock(NYVoiceType_Start);
  232. }
  233. }
  234. //队列所有音频播放完成,音频等待中
  235. -(void) onTTSPlayWait{
  236. // _messageTextView.text = [NSString stringWithFormat:@"%@\n onTTSPlayWait",_messageTextView.text];
  237. // [self scrollToBottom];
  238. self.state = NYVoiceType_Wait;
  239. NSLog(@"onTTSPlayWait");
  240. if(self.payCompletedBlock){
  241. self.payCompletedBlock(NYVoiceType_Wait);
  242. }
  243. }
  244. //恢复播放
  245. -(void) onTTSPlayResume{
  246. // _messageTextView.text = [NSString stringWithFormat:@"%@\n onTTSPlayResume",_messageTextView.text];
  247. // [self scrollToBottom];
  248. NSLog(@"onTTSPlayResume");
  249. self.state = NYVoiceType_Resume;
  250. if(self.payCompletedBlock){
  251. self.payCompletedBlock(NYVoiceType_Resume);
  252. }
  253. }
  254. //暂停播放
  255. -(void) onTTSPlayPause{
  256. // _messageTextView.text = [NSString stringWithFormat:@"%@\n onTTSPlayPause",_messageTextView.text];
  257. // [self scrollToBottom];
  258. NSLog(@"onTTSPlayPause");
  259. self.state = NYVoiceType_Pause;
  260. if(self.payCompletedBlock){
  261. self.payCompletedBlock(NYVoiceType_Pause);
  262. }
  263. }
  264. //播放中止
  265. -(void)onTTSPlayStop{
  266. _cout = 0;
  267. // _messageTextView.text = [NSString stringWithFormat:@"%@\n onTTSPlayStop",_messageTextView.text];
  268. // [self scrollToBottom];
  269. NSLog(@"onTTSPlayStop");
  270. self.state = NYVoiceType_Stop;
  271. if(self.payCompletedBlock){
  272. self.payCompletedBlock(NYVoiceType_Stop);
  273. }
  274. }
  275. //播放器异常
  276. -(void)onTTSPlayError:(QCPlayerError* _Nullable)playError{
  277. // _messageTextView.text = [NSString stringWithFormat:@"%@\n playError.code==%@\n playError.massage==%@",_messageTextView.text,@(playError.mCode),playError.message];
  278. // [self scrollToBottom];
  279. NSLog(@"playError.code==%@,playError.massage==%@",@(playError.mCode),playError.message);
  280. }
  281. //即将播放播放下一句,即将播放音频对应的句子,以及这句话utteranceId
  282. /// 即将播放播放下一句,即将播放音频对应的句子,以及这句话utteranceId
  283. /// @param text 当前播放句子的文本
  284. /// @param utteranceId 当前播放音频对应的ID
  285. -(void) onTTSPlayNextWithText:(NSString* _Nullable)text UtteranceId:(NSString* _Nullable)utteranceId{
  286. NSLog(@"text==%@,utteranceId==%@",text,utteranceId);
  287. QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
  288. if ([_player getAudioQueueSize] < 10) {
  289. if (_textArr.count > 0) {
  290. NSLog(@"AudioQueueSize=%@",@([_player getAudioQueueSize]));
  291. [tts synthesize:_textArr[0] UtteranceId:[NSString stringWithFormat:@"%d",_cout++]];
  292. [_textArr removeObjectAtIndex:0];
  293. }
  294. }
  295. }
  296. /// 当前播放的字符,当前播放的字符在所在的句子中的下标.
  297. /// @param currentWord 当前读到的单个字,是一个估算值不一定准确
  298. /// @param currentIdex 当前播放句子中读到文字的下标
  299. -(void)onTTSPlayProgressWithCurrentWord:(NSString*_Nullable)currentWord CurrentIndex:(NSInteger)currentIdex{
  300. // _messageTextView.text = [NSString stringWithFormat:@"%@\n CurrentWord==%@\n currentIdex==%@",_messageTextView.text,currentWord,@(currentIdex)];
  301. // NSLog(@"CurrentWord==%@,currentIdex==%@",currentWord,@(currentIdex));
  302. // [self scrollToBottom];
  303. }
  304. /*===============================使用自定义分割逻辑,以下为句子分割示例=====================*/
  305. #define Sentence_Count_Max 50 //单次最大请求字数,建议首句不要设置太长
  306. /*
  307. API最大请求字数详见官网文档https://cloud.tencent.com/document/product/1073/37995
  308. */
  309. - (NSMutableArray *)breakIntoSentencesFromString:(NSString *)string
  310. {
  311. NSMutableArray *stringsArr = [NSMutableArray array];
  312. NSString *splitString = @",;,;";
  313. //先按句子分割
  314. [string enumerateSubstringsInRange:NSMakeRange(0, string.length) options:NSStringEnumerationBySentences usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) {
  315. //后台接收字符不能太长
  316. const char *commitChar = [substring UTF8String];
  317. if (strlen(commitChar) > Sentence_Count_Max * 3) { //UTF-8中文的字符长度一般是3个字节,英文的长度为1
  318. //再按分号逗号分割
  319. NSArray *textArr = [self separatedString:substring split:splitString];
  320. for (NSString *object in textArr) {
  321. //再按字数分割
  322. const char *objectChar = [object UTF8String];
  323. if (strlen(objectChar) > Sentence_Count_Max * 3) {
  324. NSString *remainStr = object;
  325. while (remainStr.length > Sentence_Count_Max) {
  326. NSString *sentence = [remainStr substringToIndex:Sentence_Count_Max];
  327. if (![self isAllSplitString:sentence split:splitString]
  328. &&![self isPunct:sentence]) {
  329. [stringsArr addObject:sentence];
  330. }
  331. remainStr = [remainStr substringFromIndex:Sentence_Count_Max];
  332. }
  333. if (![self isAllSplitString:remainStr split:splitString]
  334. &&![self isPunct:remainStr]) {
  335. [stringsArr addObject:remainStr];
  336. }
  337. }
  338. else {
  339. if (![self isAllSplitString:object split:splitString]
  340. &&![self isPunct:object]) {
  341. [stringsArr addObject:object];
  342. }
  343. }
  344. }
  345. }else{
  346. if (![self isAllSplitString:substring split:splitString]
  347. &&![self isPunct:substring]) {
  348. [stringsArr addObject:substring];
  349. }
  350. }
  351. }];
  352. return stringsArr;
  353. }
  354. - (NSArray *)separatedString:(NSString *)text split:(NSString *)split {
  355. NSMutableArray *mArray = [NSMutableArray array];
  356. NSInteger loc = 0;
  357. for (NSInteger i = 0; i < [text length]; i++) {
  358. if ([split rangeOfString:[text substringWithRange:NSMakeRange(i, 1)]].location != NSNotFound) {
  359. NSString *subString = [text substringWithRange:NSMakeRange(loc, i-loc+1)];
  360. [mArray addObject:subString];
  361. loc = i + 1;
  362. }
  363. if (i + 1 == [text length] && [split rangeOfString:[text substringWithRange:NSMakeRange(i, 1)]].location == NSNotFound) { //判断最后一个字符是否包含分隔符,如果不包含把最后一句添加到数组
  364. NSString *subString = [text substringWithRange:NSMakeRange(loc, i-loc+1)];
  365. [mArray addObject:subString];
  366. }
  367. }
  368. return [mArray copy];
  369. }
  370. - (BOOL)isAllSplitString:(NSString *)text split:(NSString *)split
  371. {
  372. split = @",;,;。";
  373. if ([text length] && [split length]) {
  374. BOOL all = YES;
  375. for (NSInteger i = 0; i < [text length]; i++) {
  376. if ([split rangeOfString:[text substringWithRange:NSMakeRange(i, 1)]].location == NSNotFound) {
  377. all = NO;
  378. break;
  379. }
  380. }
  381. return all;
  382. }
  383. else {
  384. return NO;
  385. }
  386. }
  387. -(BOOL)isPunct:(NSString *)string //校验是否全为符号
  388. {
  389. NSError *error = nil;
  390. NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[\\p{P}~^<>]" options:NSRegularExpressionCaseInsensitive error:&error];
  391. NSString *modifiedString = [regex stringByReplacingMatchesInString:string options:0 range:NSMakeRange(0, [string length]) withTemplate:@""];
  392. NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
  393. NSString *trimmedStr = [modifiedString stringByTrimmingCharactersInSet:set];
  394. if (!trimmedStr.length) {
  395. return YES;
  396. }
  397. return NO;
  398. }
  399. @end