NYVoiceManager.m 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  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. #import <MediaPlayer/MediaPlayer.h>
  11. static NYVoiceManager *vomanger = nil;
  12. static dispatch_once_t onceToken;
  13. @interface NYVoiceManager ()<QCloudTTSEngineDelegate,QCloudPlayerDelegate>
  14. {
  15. NSMutableArray* _textArr;
  16. }
  17. //SDK 内置播放器
  18. @property(strong) QCloudMediaPlayer *player;
  19. // demo 层播放器,如SDK播放器不能满足需求 可以自定义播放器 如下只是一个例子
  20. //@property(strong) MediaPlayerDemo *player;
  21. @property (nonatomic,assign)float onlineSpeed;
  22. @property (nonatomic,assign)int onlineVoiceType;
  23. @property (nonatomic,assign)int onlineLanguage;
  24. @property (nonatomic,assign)float onlineVolume;
  25. @property (nonatomic,assign)float offlineSpeed;
  26. @property (nonatomic,copy)NSString *offlineVoiceType;
  27. @property (nonatomic,assign)int offlineLanguage;
  28. @property (nonatomic,assign)float offlineVolume;
  29. @property (nonatomic,assign)int responseTimeout;
  30. @property (nonatomic,assign)int requestTimeout;
  31. @property (nonatomic,assign)int checkTimeout;
  32. @property (nonatomic, copy) NSString* resource_dir;
  33. @property (nonatomic,assign)int cout; //统计合成句子数,当作utteranceId用于标记句子用
  34. #pragma mark - 播放器
  35. /*
  36. * 播放器
  37. */
  38. @property (nonatomic, strong) AVPlayer * avplayer;
  39. /*
  40. * 播放器播放状态
  41. */
  42. @property (nonatomic, assign) BOOL isPlaying;
  43. @end
  44. @implementation NYVoiceManager
  45. #pragma mark - SystemMethod
  46. + (NYVoiceManager *)sharedManager {
  47. dispatch_once(&onceToken, ^{
  48. vomanger = [[self alloc] init];
  49. });
  50. return vomanger;
  51. }
  52. - (instancetype)init {
  53. self = [super init];
  54. if (self) {
  55. [self initBaseData];
  56. }
  57. return self;
  58. }
  59. - (void)dealloc {
  60. }
  61. #pragma mark - PublicMethods
  62. // 配置
  63. - (void)ny_configureTTSVoiceDK {
  64. QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
  65. [tts cancel];
  66. [_player StopPlay];
  67. [self setOnlineParam:tts];//在线
  68. }
  69. -(void)setOnlineParam:(QCloudTTSEngine*)ttsEngine {
  70. //设置合成声音速度
  71. [ttsEngine setOnlineVoiceSpeed:_onlineSpeed];
  72. //设置合成声音类型
  73. [ttsEngine setOnlineVoiceType:_onlineVoiceType];
  74. //设置合成语种
  75. [ttsEngine setOnlineVoiceLanguage:_onlineLanguage];
  76. //设置合成声音大小默认值为0
  77. [ttsEngine setOnlineVoiceVolume:_onlineVolume];
  78. //设置响应超时时间
  79. [ttsEngine setTimeoutIntervalForResource:_responseTimeout];
  80. //设置请求超时时间
  81. [ttsEngine setTimeoutIntervalForRequest:_requestTimeout];
  82. //设置检测网络间隔时间
  83. [ttsEngine setCheckNetworkIntervalTime:_checkTimeout];
  84. }
  85. - (void)setOfflineParam:(QCloudTTSEngine*)ttsEngine{
  86. [ttsEngine setOfflineVoiceType:_offlineVoiceType];
  87. [ttsEngine setOfflineVoiceSpeed:_offlineSpeed];
  88. [ttsEngine setOfflineVoiceVolume:_offlineVolume];
  89. }
  90. //开播合成播放
  91. - (void)startPayVoiceActionText:(NSString *)text completedBlock:(nonnull NYVoicePayCompletedBlock)block {
  92. _paytext = text;
  93. self.payCompletedBlock = block;
  94. QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
  95. [self setOnlineParam:tts];
  96. //设置ProjectId 不设置默认使用0,说明:项目功能用于按项目管理云资源,可以对云资源进行分项目管理,详情见 https://console.cloud.tencent.com/project
  97. //[tts setOnlineProjectId:0];
  98. [self stopTTSBtnAction:nil];
  99. _textArr = [self breakIntoSentencesFromString:text];
  100. for (int i = 0; i < 10 - [_player getAudioQueueSize]; i++) {
  101. if (_textArr.count > 0) {
  102. [tts synthesize:_textArr[0] UtteranceId:[NSString stringWithFormat:@"%d",_cout++]];
  103. [_textArr removeObjectAtIndex:0];
  104. }
  105. }
  106. }
  107. //暂停合成播放
  108. - (void)pausePayVoiceAction {
  109. [self pauseTTSBtnAction:nil];
  110. }
  111. //恢复合成播放
  112. - (void)resumePayVoiceAction {
  113. [self resumeTTSBtnAction:nil];
  114. }
  115. //停止合成播放
  116. - (void)stopPayVoiceAction {
  117. [self stopTTSBtnAction:nil];
  118. }
  119. - (IBAction)stopTTSBtnAction:(id)sender {
  120. QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
  121. [tts cancel];
  122. [_player StopPlay];
  123. [_textArr removeAllObjects];
  124. }
  125. - (IBAction)pauseTTSBtnAction:(id)sender {
  126. [_player PausePlay];
  127. }
  128. - (IBAction)resumeTTSBtnAction:(id)sender {
  129. [_player ResumePlay];
  130. }
  131. #pragma mark - PrivateMethods
  132. - (void)initBaseData {
  133. _cout = 0;
  134. _textArr = [NSMutableArray array];
  135. QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
  136. //如果使用STS临时证书鉴权时需要设置Token
  137. [tts setOnlineAuthParam:QDApp_Id SecretId:QDSecret_ID SecretKey:QDSecret_Key Token:nil];
  138. [tts engineInit:0 Delegate:self];
  139. [tts setEnableDebugLog:YES];
  140. //SDK 内置播放器
  141. _player = [[QCloudMediaPlayer alloc]init];
  142. // demo 层播放器,如SDK播放器不能满足需求 可以自定义播放器 如下只是一个例子 源码在MediaPlayerDemo.m文件 可自行修改
  143. //_player = [[MediaPlayerDemo alloc]init];
  144. _player.playerDelegate = self;
  145. //离线参数
  146. //在线参数
  147. _onlineSpeed = 0;
  148. _onlineVoiceType = 1001;
  149. //主语言类型:1-中文(默认)2-英文
  150. _onlineLanguage = 1;
  151. _onlineVolume = 0;
  152. //连接超时默认15000ms(15s) 范围[500,30000] 单位ms , Mix模式下建议调小此值,以获得更好的体验
  153. _responseTimeout = 15*1000;
  154. //读取超时超时默认30000ms(30s) 范围[2200,60000] 单位ms, Mix模式下建议调小此值,以获得更好的体验
  155. _requestTimeout = 30 * 1000;
  156. //离线参数,如果您下载的是在线版SDK,以下参数不需要设置,请忽略
  157. _offlineSpeed = 1.0;//离线语速[0.5,2.0]
  158. //voiceType 离线音色名称,名称配置位于音色资源目录tts_resource\voices\config.json 中,可自行增删音色
  159. _offlineVoiceType = @"pb";
  160. _offlineVolume = 1.0;//离线音量 > 0
  161. /*_checkTimeout 大于等于0 单位s, 等于0时持续检测,直到成功
  162. Mix模式下,已经连接网络,但出现网络错误后的检测间隔时间,用于从离线模式自动切换回在线模式,默认值5分钟
  163. 注意:每次检测时将使用所入参的一句文本请求服务器,如果后端合成成功将会额外消耗该句字数的合成额度
  164. */
  165. _checkTimeout = 300;
  166. // JSJP_APP_VOICE_SET
  167. @try {
  168. // NSDictionary *dict = @{
  169. // @"onlineSpeed":@"0",//设置合成声音速度0默认 1正常 1.2倍
  170. // @"onlineVoiceType":@"1001",//设置合成声音类型
  171. // @"onlineLanguage":@"1",//设置合成语种-1中文 2英文
  172. // @"onlineVolume":@"0",//设置合成声音大小默认值为0
  173. // };
  174. NSDictionary *dict = RQ_COMMON_MANAGER.JSJP_APP_VOICE_SET;
  175. if(dict){ //读取服务器配置
  176. _onlineSpeed = [dict[@"onlineSpeed"] floatValue];
  177. _onlineVoiceType = [dict[@"onlineVoiceType"] intValue];
  178. _onlineLanguage = [dict[@"onlineLanguage"] intValue];
  179. _onlineVolume = [dict[@"onlineVolume"] floatValue];
  180. }
  181. } @catch (NSException *exception) {
  182. NSLog(@"%@",exception.description);
  183. }
  184. }
  185. #pragma mark -- QCloudTTSEngineDelegate
  186. //合成结果返回
  187. //---------QCloudTTSEngineDelegate---------
  188. /// 合成回调
  189. /// @param data 语音数据
  190. /// @param utteranceId 语句id
  191. /// @param text 文本
  192. /// @param type 引擎类型 0:在线 1:离线
  193. ///
  194. -(void) onSynthesizeData:(NSData *_Nullable)data UtteranceId:(NSString *_Nullable)utteranceId Text:(NSString *_Nullable)text EngineType:(NSInteger)type RequestId:(NSString*)requestId{
  195. NSLog(@"text====%@,utteranceId ==%@,type==%ld,requestId=%@",text,utteranceId,(long)type,requestId);
  196. [_player enqueueWithData:data Text:text UtteranceId:utteranceId];
  197. // //通过保存音频的URL播放
  198. // //NSString *str = [self filePathWithName:@"tmp.mp3"];
  199. // //NSURL * url = [NSURL URLWithString:str];
  200. // //[_player enqueueWithFile:url Text:text UtteranceId:utteranceId];
  201. //
  202. //
  203. // _messageTextView.text = [NSString stringWithFormat:@"%@\n text=%@\n utteranceId=%@ requestId=%@",_messageTextView.text,text,utteranceId,requestId];
  204. // [self scrollToBottom];
  205. //
  206. // NSLog(@"onSynthesizeData %@",@(data.length));
  207. }
  208. /// 错误回调
  209. /// @param error 错误信息
  210. /// @param utteranceId utteranceId
  211. /// @param text text
  212. -(void) onError:(TtsError *_Nullable)error UtteranceId:(NSString *_Nullable)utteranceId Text:(NSString *_Nullable)text{
  213. // if (error.serviceError != nil && _ttsMode == TTS_MODE_MIX) {
  214. // //实际业务上判断一下,如果是混合模式下返回在线合成的后端错误码,应当忽略可不做处理
  215. // //SDK内会继续调用离线合成继续工作,如果没有日志需求,这里直接return忽略即可
  216. // //return;
  217. // }else{
  218. // QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
  219. // [tts cancel];
  220. // }
  221. //
  222. // 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];
  223. // _messageTextView.text = msg;
  224. // [self scrollToBottom];
  225. // NSLog(@"onError %@",msg);
  226. }
  227. //离线或者混合模式下,调用初始化后必须收到此回调,并且OfflineAuthInfo.err_code为0时,才可以调用合成接口,如果err_code不为0,请检查参数或者网络,重新init
  228. - (void)onOfflineAuthInfo:(QCloudOfflineAuthInfo * _Nonnull)OfflineAuthInfo {
  229. // 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];
  230. //
  231. // NSLog(@"%@",msg);
  232. // _messageTextView.text = msg;
  233. // [self scrollToBottom];
  234. }
  235. //---------QCloudPlayerDelegate---------
  236. //播放开始
  237. -(void) onTTSPlayStart{
  238. // _messageTextView.text = [NSString stringWithFormat:@"%@\n onTTSPlayStart",_messageTextView.text];
  239. // [self scrollToBottom];
  240. NSLog(@"onTTSPlayStart");
  241. self.state = NYVoiceType_Start;
  242. if(self.payCompletedBlock){
  243. self.payCompletedBlock(NYVoiceType_Start);
  244. }
  245. }
  246. //队列所有音频播放完成,音频等待中
  247. -(void) onTTSPlayWait{
  248. // _messageTextView.text = [NSString stringWithFormat:@"%@\n onTTSPlayWait",_messageTextView.text];
  249. // [self scrollToBottom];
  250. self.state = NYVoiceType_Wait;
  251. NSLog(@"onTTSPlayWait");
  252. if(self.payCompletedBlock){
  253. self.payCompletedBlock(NYVoiceType_Wait);
  254. }
  255. }
  256. //恢复播放
  257. -(void) onTTSPlayResume{
  258. // _messageTextView.text = [NSString stringWithFormat:@"%@\n onTTSPlayResume",_messageTextView.text];
  259. // [self scrollToBottom];
  260. NSLog(@"onTTSPlayResume");
  261. self.state = NYVoiceType_Resume;
  262. if(self.payCompletedBlock){
  263. self.payCompletedBlock(NYVoiceType_Resume);
  264. }
  265. }
  266. //暂停播放
  267. -(void) onTTSPlayPause{
  268. // _messageTextView.text = [NSString stringWithFormat:@"%@\n onTTSPlayPause",_messageTextView.text];
  269. // [self scrollToBottom];
  270. NSLog(@"onTTSPlayPause");
  271. self.state = NYVoiceType_Pause;
  272. if(self.payCompletedBlock){
  273. self.payCompletedBlock(NYVoiceType_Pause);
  274. }
  275. }
  276. //播放中止
  277. -(void)onTTSPlayStop{
  278. _cout = 0;
  279. // _messageTextView.text = [NSString stringWithFormat:@"%@\n onTTSPlayStop",_messageTextView.text];
  280. // [self scrollToBottom];
  281. NSLog(@"onTTSPlayStop");
  282. self.state = NYVoiceType_Stop;
  283. if(self.payCompletedBlock){
  284. self.payCompletedBlock(NYVoiceType_Stop);
  285. }
  286. }
  287. //播放器异常
  288. -(void)onTTSPlayError:(QCPlayerError* _Nullable)playError{
  289. // _messageTextView.text = [NSString stringWithFormat:@"%@\n playError.code==%@\n playError.massage==%@",_messageTextView.text,@(playError.mCode),playError.message];
  290. // [self scrollToBottom];
  291. NSLog(@"playError.code==%@,playError.massage==%@",@(playError.mCode),playError.message);
  292. }
  293. //即将播放播放下一句,即将播放音频对应的句子,以及这句话utteranceId
  294. /// 即将播放播放下一句,即将播放音频对应的句子,以及这句话utteranceId
  295. /// @param text 当前播放句子的文本
  296. /// @param utteranceId 当前播放音频对应的ID
  297. -(void) onTTSPlayNextWithText:(NSString* _Nullable)text UtteranceId:(NSString* _Nullable)utteranceId{
  298. NSLog(@"text==%@,utteranceId==%@",text,utteranceId);
  299. QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
  300. if ([_player getAudioQueueSize] < 10) {
  301. if (_textArr.count > 0) {
  302. NSLog(@"AudioQueueSize=%@",@([_player getAudioQueueSize]));
  303. [tts synthesize:_textArr[0] UtteranceId:[NSString stringWithFormat:@"%d",_cout++]];
  304. [_textArr removeObjectAtIndex:0];
  305. }
  306. }
  307. }
  308. /// 当前播放的字符,当前播放的字符在所在的句子中的下标.
  309. /// @param currentWord 当前读到的单个字,是一个估算值不一定准确
  310. /// @param currentIdex 当前播放句子中读到文字的下标
  311. -(void)onTTSPlayProgressWithCurrentWord:(NSString*_Nullable)currentWord CurrentIndex:(NSInteger)currentIdex{
  312. // _messageTextView.text = [NSString stringWithFormat:@"%@\n CurrentWord==%@\n currentIdex==%@",_messageTextView.text,currentWord,@(currentIdex)];
  313. // NSLog(@"CurrentWord==%@,currentIdex==%@",currentWord,@(currentIdex));
  314. // [self scrollToBottom];
  315. }
  316. //播放在线 url 音频
  317. - (void)startPayVoiceActionUrl:(NSString *)url {
  318. //重置播放器
  319. AVPlayerItem * songItem = [[AVPlayerItem alloc]initWithURL:[NSURL URLWithString:url]];
  320. if (self.avplayer == nil) {
  321. self.avplayer = [[AVPlayer alloc]initWithPlayerItem:songItem];
  322. }else {
  323. [self.avplayer replaceCurrentItemWithPlayerItem:songItem];
  324. }
  325. //开始播放
  326. [self.avplayer play];
  327. //添加KVO
  328. [self addObserver];
  329. self.state = NYVoiceType_Start;
  330. if(self.payCompletedBlock){
  331. self.payCompletedBlock(NYVoiceType_Start);
  332. }
  333. }
  334. - (void)startPayVoiceActionUrl:(NSString *)url completedBlock:(nonnull NYVoicePayCompletedBlock)block{
  335. self.payCompletedBlock = block;
  336. [self startPayVoiceActionUrl:url];
  337. }
  338. //暂停合成播放
  339. - (void)pausePayVoiceActionUrl {
  340. if(self.avplayer){
  341. [self.avplayer pause];
  342. self.state = NYVoiceType_Pause;
  343. if(self.payCompletedBlock){
  344. self.payCompletedBlock(NYVoiceType_Pause);
  345. }
  346. }
  347. }
  348. //停止合成播放
  349. - (void)stopPayVoiceActionUrl {
  350. if(self.avplayer){
  351. [self.avplayer pause];
  352. self.avplayer = nil;
  353. self.state = NYVoiceType_Stop;
  354. if(self.payCompletedBlock){
  355. self.payCompletedBlock(NYVoiceType_Stop);
  356. }
  357. }
  358. }
  359. //恢复合成播放
  360. - (void)resumePayVoiceActionUrl {
  361. if(self.avplayer){
  362. [self.avplayer play];
  363. self.state = NYVoiceType_Resume;
  364. if(self.payCompletedBlock){
  365. self.payCompletedBlock(NYVoiceType_Resume);
  366. }
  367. }
  368. }
  369. #pragma mark AVpalyer url
  370. /*
  371. * 播放器播放状态
  372. */
  373. - (BOOL)isPlaying {
  374. return self.avplayer.rate == 1;
  375. }
  376. #pragma mark - KVO
  377. - (void)addObserver {
  378. AVPlayerItem * songItem = self.avplayer.currentItem;
  379. //给AVPlayerItem添加播放完成通知
  380. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:songItem];
  381. // //更新播放器进度
  382. // WeakSelf(weakSelf)
  383. // _timeObserve = [self.avplayer addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
  384. // float current = CMTimeGetSeconds(time);
  385. // float total = CMTimeGetSeconds(songItem.duration);
  386. //// SuLog(@"%f, %f",current, total);
  387. // if (current) {
  388. // weakSelf.progress = current / total;
  389. // weakSelf.playTime = [NSString stringWithFormat:@"%.f",current];
  390. // weakSelf.playDuration = [NSString stringWithFormat:@"%.2f",total];
  391. // }
  392. // }];
  393. //监控状态属性,注意AVPlayer也有一个status属性,通过监控它的status也可以获得播放状态
  394. [songItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
  395. //监控网络加载情况属性
  396. [songItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
  397. }
  398. - (void)removeObserver {
  399. AVPlayerItem * songItem = self.avplayer.currentItem;
  400. [[NSNotificationCenter defaultCenter] removeObserver:self];
  401. // if (_timeObserve) {
  402. // [self.avplayer removeTimeObserver:_timeObserve];
  403. // _timeObserve = nil;
  404. // }
  405. [songItem removeObserver:self forKeyPath:@"status"];
  406. [songItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
  407. [self.avplayer replaceCurrentItemWithPlayerItem:nil];
  408. }
  409. - (void)playbackFinished:(NSNotification *)notice {
  410. NSLog(@"播放完成");
  411. if(self.payCompletedBlock){
  412. self.payCompletedBlock(NYVoiceType_Wait);
  413. }
  414. // [self playNext];
  415. }
  416. /**
  417. * 通过KVO监控播放器状态
  418. *
  419. * @param keyPath 监控属性
  420. * @param object 监视器
  421. * @param change 状态改变
  422. * @param context 上下文
  423. */
  424. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
  425. AVPlayerItem * songItem = object;
  426. if ([keyPath isEqualToString:@"status"]) {
  427. switch (self.avplayer.status) {
  428. case AVPlayerStatusUnknown:
  429. NSLog(@"KVO:未知状态");
  430. break;
  431. case AVPlayerStatusReadyToPlay:
  432. self.state = NYVoiceType_Start;
  433. NSLog(@"KVO:准备完毕");
  434. break;
  435. case AVPlayerStatusFailed:
  436. NSLog(@"KVO:加载失败");
  437. break;
  438. default:
  439. break;
  440. }
  441. // SendNotify(SONGPLAYSTATUSCHANGE, nil)
  442. }
  443. if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
  444. NSArray * array = songItem.loadedTimeRanges;
  445. CMTimeRange timeRange = [array.firstObject CMTimeRangeValue]; //本次缓冲的时间范围
  446. NSTimeInterval totalBuffer = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration); //缓冲总长度
  447. // SuLog(@"共缓冲%.2f",totalBuffer);
  448. }
  449. }
  450. /*===============================使用自定义分割逻辑,以下为句子分割示例=====================*/
  451. #define Sentence_Count_Max 50 //单次最大请求字数,建议首句不要设置太长
  452. /*
  453. API最大请求字数详见官网文档https://cloud.tencent.com/document/product/1073/37995
  454. */
  455. - (NSMutableArray *)breakIntoSentencesFromString:(NSString *)string
  456. {
  457. NSMutableArray *stringsArr = [NSMutableArray array];
  458. NSString *splitString = @",;,;";
  459. //先按句子分割
  460. [string enumerateSubstringsInRange:NSMakeRange(0, string.length) options:NSStringEnumerationBySentences usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) {
  461. //后台接收字符不能太长
  462. const char *commitChar = [substring UTF8String];
  463. if (strlen(commitChar) > Sentence_Count_Max * 3) { //UTF-8中文的字符长度一般是3个字节,英文的长度为1
  464. //再按分号逗号分割
  465. NSArray *textArr = [self separatedString:substring split:splitString];
  466. for (NSString *object in textArr) {
  467. //再按字数分割
  468. const char *objectChar = [object UTF8String];
  469. if (strlen(objectChar) > Sentence_Count_Max * 3) {
  470. NSString *remainStr = object;
  471. while (remainStr.length > Sentence_Count_Max) {
  472. NSString *sentence = [remainStr substringToIndex:Sentence_Count_Max];
  473. if (![self isAllSplitString:sentence split:splitString]
  474. &&![self isPunct:sentence]) {
  475. [stringsArr addObject:sentence];
  476. }
  477. remainStr = [remainStr substringFromIndex:Sentence_Count_Max];
  478. }
  479. if (![self isAllSplitString:remainStr split:splitString]
  480. &&![self isPunct:remainStr]) {
  481. [stringsArr addObject:remainStr];
  482. }
  483. }
  484. else {
  485. if (![self isAllSplitString:object split:splitString]
  486. &&![self isPunct:object]) {
  487. [stringsArr addObject:object];
  488. }
  489. }
  490. }
  491. }else{
  492. if (![self isAllSplitString:substring split:splitString]
  493. &&![self isPunct:substring]) {
  494. [stringsArr addObject:substring];
  495. }
  496. }
  497. }];
  498. return stringsArr;
  499. }
  500. - (NSArray *)separatedString:(NSString *)text split:(NSString *)split {
  501. NSMutableArray *mArray = [NSMutableArray array];
  502. NSInteger loc = 0;
  503. for (NSInteger i = 0; i < [text length]; i++) {
  504. if ([split rangeOfString:[text substringWithRange:NSMakeRange(i, 1)]].location != NSNotFound) {
  505. NSString *subString = [text substringWithRange:NSMakeRange(loc, i-loc+1)];
  506. [mArray addObject:subString];
  507. loc = i + 1;
  508. }
  509. if (i + 1 == [text length] && [split rangeOfString:[text substringWithRange:NSMakeRange(i, 1)]].location == NSNotFound) { //判断最后一个字符是否包含分隔符,如果不包含把最后一句添加到数组
  510. NSString *subString = [text substringWithRange:NSMakeRange(loc, i-loc+1)];
  511. [mArray addObject:subString];
  512. }
  513. }
  514. return [mArray copy];
  515. }
  516. - (BOOL)isAllSplitString:(NSString *)text split:(NSString *)split
  517. {
  518. split = @",;,;。";
  519. if ([text length] && [split length]) {
  520. BOOL all = YES;
  521. for (NSInteger i = 0; i < [text length]; i++) {
  522. if ([split rangeOfString:[text substringWithRange:NSMakeRange(i, 1)]].location == NSNotFound) {
  523. all = NO;
  524. break;
  525. }
  526. }
  527. return all;
  528. }
  529. else {
  530. return NO;
  531. }
  532. }
  533. -(BOOL)isPunct:(NSString *)string //校验是否全为符号
  534. {
  535. NSError *error = nil;
  536. NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[\\p{P}~^<>]" options:NSRegularExpressionCaseInsensitive error:&error];
  537. NSString *modifiedString = [regex stringByReplacingMatchesInString:string options:0 range:NSMakeRange(0, [string length]) withTemplate:@""];
  538. NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
  539. NSString *trimmedStr = [modifiedString stringByTrimmingCharactersInSet:set];
  540. if (!trimmedStr.length) {
  541. return YES;
  542. }
  543. return NO;
  544. }
  545. @end