一、前言
从我去年集成好安卓的代码,已经过去了大半年了,sdk的版本也从1.5升级到了2.0,近期终于完成了ios的集成,希望可以帮助到大家。本人)Objective C写的不好,代码可能不是那么大的完备,仅作参考学习。
二、下载sdk
需要登陆腾讯云,找到语音技术,下载sdk
文档链接 腾讯tts文档
三、 iOS 原生模块
1. 集成frameworks
从sdk文件夹里面找到QCloudTTS.xcframework,引入到项目中。
2. TTSModule.h
注意1:我这里用了MediaPlayerDemo
,也可以使用sdk内置播放器
//
// TTSModule.h
//
// Created by cauyyl on 2023/5/24.
//
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
//#import <React/RCTLog.h>
#import "MediaPlayerDemo.h"
//@interface TTSModule : RCTEventEmitter <RCTBridgeModule>
@interface TTSModule : NSObject <RCTBridgeModule>
@property(strong) MediaPlayerDemo *player;
@property (nonatomic)int cout; //统计合成句子数,当作utteranceId用于标记句子用
@property (atomic)NSMutableArray* textArr;
@end
3.TTSModule.m
注意1:setOnlineAuthParam
的参数需要改成你的账户信息
注意2:我简化了很多参数设置,参数都被我写死了。参数的详情可以参考demo里面LongTextViewController.m
我的代码也是主要参考的LongTextViewController.m
注意3:我去掉了离线模式
//
// TTSModule.m
//
// Created by cauyyl on 2023/5/24.
//
#import "TTSModule.h"
#import <QCloudTTS/QCloudTTSEngine.h>
@implementation TTSModule
RCTResponseSenderBlock ttsCallback;
- (void)dealloc{
_player = nil;
[QCloudTTSEngine instanceRelease];
}
RCT_EXPORT_MODULE(TTS);
RCT_EXPORT_METHOD(pause){
if(_player){
[_player PausePlay];
}
}
RCT_EXPORT_METHOD(destroy){
[_player StopPlay];
[_textArr removeAllObjects];
QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
[tts cancel];
_player = nil;
[QCloudTTSEngine instanceRelease];
}
-(void) stop{
[_player StopPlay];
[_textArr removeAllObjects];
QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
[tts cancel];
}
RCT_EXPORT_METHOD(playLongText: (NSString *)title content:(NSString *)content forceStart:(BOOL *)forceStart callback:(RCTResponseSenderBlock)callback) {
ttsCallback=callback;
NSLog(@"showWithMessage");
NSLog(@"title=%@",title);
NSLog(@"content=%@",content);
// if(forceStart){
// NSLog(@"forceStart");
// }
//
QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
if(_player){
if(forceStart){
NSLog(@"forceStart");
_cout=0;
[self stop];
_textArr = [self breakIntoSentencesFromString:content];
// NSLog(@"_textArr38=%@",_textArr);
NSLog(@"_cout63=%d",_cout);
for (int i = 0; i < 10 - [_player getAudioQueueSize]; i++) {
if (_textArr.count > 0) {
[tts synthesize:_textArr[0] UtteranceId:[NSString stringWithFormat:@"%d",_cout++]];
[_textArr removeObjectAtIndex:0];
}
}
}else {
[_player ResumePlay];
ttsCallback(@[@(1)]);
// [self sendEventWithName:@"ttsEvents" body:@{@"code":@"1",}];
}
}else{
_cout = 0;
_textArr = [NSMutableArray array];
//如果使用STS临时证书鉴权时需要设置Token
NSLog(@"鉴权");
[tts setOnlineAuthParam:123 SecretId:@"AK****Hp7" SecretKey:@"D9***c2" Token:nil];
[tts engineInit:0 Delegate:self];
// [tts setEnableDebugLog:YES];
//SDK 内置播放器
// _player = [[QCloudMediaPlayer alloc]init];
// demo 层播放器,如SDK播放器不能满足需求 可以自定义播放器 如下只是一个例子 源码在MediaPlayerDemo.m文件 可自行修改
_player = [[MediaPlayerDemo alloc]init];
_player.playerDelegate = self;
[self setOnlineParam:tts];
_textArr = [self breakIntoSentencesFromString:content];
// NSLog(@"_textArr68=%@",_textArr);
for (int i = 0; i < 10 - [_player getAudioQueueSize]; i++) {
if (_textArr.count > 0) {
[tts synthesize:_textArr[0] UtteranceId:[NSString stringWithFormat:@"%d",_cout++]];
[_textArr removeObjectAtIndex:0];
}
}
}
}
-(void)setOnlineParam:(QCloudTTSEngine*)ttsEngine{
//设置合成声音速度
[ttsEngine setOnlineVoiceSpeed:0];
//设置合成声音类型
[ttsEngine setOnlineVoiceType:101001];
//设置合成语种
[ttsEngine setOnlineVoiceLanguage:1];
//设置合成声音大小默认值为0
[ttsEngine setOnlineVoiceVolume:0];
//设置响应超时时间
[ttsEngine setTimeoutIntervalForResource :15*1000];
//设置请求超时时间
[ttsEngine setTimeoutIntervalForRequest: 30 * 1000];
//设置检测网络间隔时间
[ttsEngine setCheckNetworkIntervalTime:5*60];
}
//合成结果返回
//---------QCloudTTSEngineDelegate---------
/// 合成回调
/// @param data 语音数据
/// @param utteranceId 语句id
/// @param text 文本
/// @param type 引擎类型 0:在线 1:离线
///
-(void) onSynthesizeData:(NSData *_Nullable)data UtteranceId:(NSString *_Nullable)utteranceId Text:(NSString *_Nullable)text EngineType:(NSInteger)type RequestId:(NSString*)requestId{
NSLog(@"text====%@,utteranceId ==%@,type==%ld,requestId=%@",text,utteranceId,(long)type,requestId);
[_player enqueueWithData:data Text:text UtteranceId:utteranceId];
//通过保存音频的URL播放
//NSString *str = [self filePathWithName:@"tmp.mp3"];
//NSURL * url = [NSURL URLWithString:str];
//[_player enqueueWithFile:url Text:text UtteranceId:utteranceId];
NSLog(@"onSynthesizeData %@",@(data.length));
}
/// 错误回调
/// @param error 错误信息
/// @param utteranceId utteranceId
/// @param text text
-(void) onError:(TtsError *_Nullable)error UtteranceId:(NSString *_Nullable)utteranceId Text:(NSString *_Nullable)text{
NSLog(@"onError 273");
}
//---------QCloudPlayerDelegate---------
//播放开始
-(void) onTTSPlayStart{
NSLog(@"onTTSPlayStart 开始播放");
}
//队列所有音频播放完成,音频等待中
-(void) onTTSPlayWait{
// [self sendEventWithName:@"ttsEvents" body:@{@"code":@"3",}];
ttsCallback(@[@(3)]);
NSLog(@"onTTSPlayWait 播放完成,等待音频数据");
}
//恢复播放
-(void) onTTSPlayResume{
NSLog(@"onTTSPlayResume");
}
//暂停播放
-(void) onTTSPlayPause{
NSLog(@"onTTSPlayPause");
}
//播放中止
-(void)onTTSPlayStop{
_cout = 0;
NSLog(@"onTTSPlayStop");
}
//播放器异常
-(void)onTTSPlayError:(QCPlayerError* _Nullable)playError{
NSLog(@"playError.code==%@,playError.massage==%@",@(playError.mCode),playError.message);
}
//即将播放播放下一句,即将播放音频对应的句子,以及这句话utteranceId
/// 即将播放播放下一句,即将播放音频对应的句子,以及这句话utteranceId
/// @param text 当前播放句子的文本
/// @param utteranceId 当前播放音频对应的ID
-(void) onTTSPlayNextWithText:(NSString* _Nullable)text UtteranceId:(NSString* _Nullable)utteranceId{
NSLog(@"text==%@,utteranceId==%@",text,utteranceId);
QCloudTTSEngine *tts = [QCloudTTSEngine getShareInstance];
if ([_player getAudioQueueSize] < 10) {
if (_textArr.count > 0) {
NSLog(@"AudioQueueSize=%@",@([_player getAudioQueueSize]));
[tts synthesize:_textArr[0] UtteranceId:[NSString stringWithFormat:@"%d",_cout++]];
[_textArr removeObjectAtIndex:0];
}
}
}
/// 当前播放的字符,当前播放的字符在所在的句子中的下标.
/// @param currentWord 当前读到的单个字,是一个估算值不一定准确
/// @param currentIdex 当前播放句子中读到文字的下标
-(void)onTTSPlayProgressWithCurrentWord:(NSString*_Nullable)currentWord CurrentIndex:(NSInteger)currentIdex{
NSLog(@"CurrentWord==%@,currentIdex==%@",currentWord,@(currentIdex));
}
/*===============================使用自定义分割逻辑,以下为句子分割示例=====================*/
#define Sentence_Count_Max 50 //单次最大请求字数,建议首句不要设置太长
/*
API最大请求字数详见官网文档https://cloud.tencent.com/document/product/1073/37995
*/
- (NSMutableArray *)breakIntoSentencesFromString:(NSString *)string
{
NSMutableArray *stringsArr = [NSMutableArray array];
NSString *splitString = @",;,;";
//先按句子分割
[string enumerateSubstringsInRange:NSMakeRange(0, string.length) options:NSStringEnumerationBySentences usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) {
//后台接收字符不能太长
const char *commitChar = [substring UTF8String];
if (strlen(commitChar) > Sentence_Count_Max * 3) { //UTF-8中文的字符长度一般是3个字节,英文的长度为1
//再按分号逗号分割
NSArray *textArr = [self separatedString:substring split:splitString];
for (NSString *object in textArr) {
//再按字数分割
const char *objectChar = [object UTF8String];
if (strlen(objectChar) > Sentence_Count_Max * 3) {
NSString *remainStr = object;
while (remainStr.length > Sentence_Count_Max) {
NSString *sentence = [remainStr substringToIndex:Sentence_Count_Max];
if (![self isAllSplitString:sentence split:splitString]
&&![self isPunct:sentence]) {
[stringsArr addObject:sentence];
}
remainStr = [remainStr substringFromIndex:Sentence_Count_Max];
}
if (![self isAllSplitString:remainStr split:splitString]
&&![self isPunct:remainStr]) {
[stringsArr addObject:remainStr];
}
}
else {
if (![self isAllSplitString:object split:splitString]
&&![self isPunct:object]) {
[stringsArr addObject:object];
}
}
}
}else{
if (![self isAllSplitString:substring split:splitString]
&&![self isPunct:substring]) {
[stringsArr addObject:substring];
}
}
}];
// NSLog(@"stringsArr=%@",stringsArr);
return stringsArr;
}
- (NSArray *)separatedString:(NSString *)text split:(NSString *)split {
NSMutableArray *mArray = [NSMutableArray array];
NSInteger loc = 0;
for (NSInteger i = 0; i < [text length]; i++) {
if ([split rangeOfString:[text substringWithRange:NSMakeRange(i, 1)]].location != NSNotFound) {
NSString *subString = [text substringWithRange:NSMakeRange(loc, i-loc+1)];
[mArray addObject:subString];
loc = i + 1;
}
if (i + 1 == [text length] && [split rangeOfString:[text substringWithRange:NSMakeRange(i, 1)]].location == NSNotFound) { //判断最后一个字符是否包含分隔符,如果不包含把最后一句添加到数组
NSString *subString = [text substringWithRange:NSMakeRange(loc, i-loc+1)];
[mArray addObject:subString];
}
}
return [mArray copy];
}
- (BOOL)isAllSplitString:(NSString *)text split:(NSString *)split
{
split = @",;,;。";
if ([text length] && [split length]) {
BOOL all = YES;
for (NSInteger i = 0; i < [text length]; i++) {
if ([split rangeOfString:[text substringWithRange:NSMakeRange(i, 1)]].location == NSNotFound) {
all = NO;
break;
}
}
return all;
}
else {
return NO;
}
}
-(BOOL)isPunct:(NSString *)string //校验是否全为符号
{
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[\\p{P}~^<>]" options:NSRegularExpressionCaseInsensitive error:&error];
NSString *modifiedString = [regex stringByReplacingMatchesInString:string options:0 range:NSMakeRange(0, [string length]) withTemplate:@""];
NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSString *trimmedStr = [modifiedString stringByTrimmingCharactersInSet:set];
if (!trimmedStr.length) {
return YES;
}
return NO;
}
/*=======================================使用自定义分割逻辑====================================================*/
/*
#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
四、前端使用
NativeModules.TTS.playLongText(
'测试文章',
'三月的春风是彩色的,染红了迎春花,描青了山峰,绘绿了秧苗,吹红了我们胸前的红领巾,同时也掀起了我们学习雷锋的热潮。说起雷锋,我们并不陌生,自然会想起一段话:“如果你是一滴水,你是否滋润了一方土地?如果你是一缕阳光,你是否照亮了一分黑暗?如果你是一粒粮食,你是否哺育了生命?如果你是一颗最小的螺丝钉,你是否坚守着你生活的岗位?”。。。。。。',
false,
() => {
navigation.navigate('BishenHuiYuan', {
source: 'correct-tts',
courseType: 'correct-tts',
})
},
)
五、调试和报错
1. arm64报错
- 在built setting里找到 Architectures
- 在Excluded Architectures添加arm64