Newer
Older
react-native-hi-baidu-tts / ios / HiSpeechSynthesizer.m
xiaobaoafei on 29 Apr 2022 添加一些设置方法
//
//  HiSpeechSynthesizer.m
//  HiBaiduTts
//
//  Created by 连仁辉 on 2022/4/22.
//  Copyright © 2022 Facebook. All rights reserved.
//

#import "HiSpeechSynthesizer.h"
#import "BDSSpeechSynthesizer.h"
#import "TTDFileReader.h"
#import "BDSSpeechSynthesizerParams.h"
#import <AVFoundation/AVFoundation.h>

#define READ_SYNTHESIS_TEXT_FROM_FILE (NO)

static BOOL isSpeak = YES;
static BOOL textFromFile = READ_SYNTHESIS_TEXT_FROM_FILE;
static BOOL displayAllSentences = !READ_SYNTHESIS_TEXT_FROM_FILE;

@interface HiSpeechSynthesizer ()
@property (nonatomic,assign)BOOL pauseOrResumeEnable;
@property (nonatomic,assign)BOOL cancelEnable;

@property (nonatomic,copy)NSString* apiKey;
@property (nonatomic,copy)NSString* secretKey;

@property (nonatomic,strong)NSMutableArray* synthesisTexts;
@property (nonatomic,strong)NSMutableArray* addTextQueue; /*used with textFromFile*/
@end

@implementation HiSpeechSynthesizer
+ (BOOL)isFileSynthesisEnabled{
    return textFromFile;
}
+ (BOOL)isSpeakEnabled{
    return isSpeak;
}
+ (void)setFileSynthesisEnabled:(BOOL)isEnabled{
    textFromFile = isEnabled;
    displayAllSentences = !textFromFile;
}
+ (void)setSpeakEnabled:(BOOL)isEnabled{
    isSpeak = isEnabled;
}

- (instancetype)initApiKey:(NSString *)apiKey secretKey:(NSString *)secretKey {
    self = [super init];
    if(self){
        self.synthesisTexts = [[NSMutableArray alloc] init];
        self.apiKey = apiKey;
        self.secretKey = secretKey;
        self.pauseOrResumeEnable = FALSE;
        self.cancelEnable = FALSE;
        [self configureSDK];
    }
    return self;
}

- (void)setSpeaker:(NSString *)speaker {
    NSError *err = [[BDSSpeechSynthesizer sharedInstance] setSynthParam:speaker forKey:BDS_SYNTHESIZER_PARAM_SPEAKER];
    if(err){
        [self displayError:err withTitle:@"Failed set online TTS speaker"];
    }
}

- (void)setVolume:(NSNumber *)value{
    NSError* err = [[BDSSpeechSynthesizer sharedInstance] setSynthParam:value forKey:BDS_SYNTHESIZER_PARAM_VOLUME];
    if(err){
        [self displayError:err withTitle:@"Failed set synth volume"];
    }
}

- (void)setSpeed:(NSNumber *)value{
    NSError* err = [[BDSSpeechSynthesizer sharedInstance] setSynthParam:value forKey:BDS_SYNTHESIZER_PARAM_SPEED];
    if(err){
        [self displayError:err withTitle:@"Failed set synth speed"];
    }
}

- (void)setPitch:(NSNumber *)value{
    NSError* err = [[BDSSpeechSynthesizer sharedInstance] setSynthParam:value forKey:BDS_SYNTHESIZER_PARAM_PITCH];
    if(err){
        [self displayError:err withTitle:@"Failed set synth pitch"];
    }
}

- (void)setAudioSessionEnable:(NSNumber *)value {
    NSError* err = [[BDSSpeechSynthesizer sharedInstance] setSynthParam:value forKey:BDS_SYNTHESIZER_PARAM_ENABLE_AVSESSION_MGMT];
    if(err){
        [self displayError:err withTitle:@"Failed set synth session enable"];
    }
}

- (void)setAudioSessionCategory:(NSNumber *)category{
    int _category = category.intValue;
    NSString *avCategory = nil;
    switch (_category) {
        case 0:
            avCategory = AVAudioSessionCategoryAmbient;
            break;
        case 1:
            avCategory = AVAudioSessionCategorySoloAmbient;
            break;
        case 2:
            avCategory = AVAudioSessionCategoryPlayback;
            break;
        case 3:
            avCategory = AVAudioSessionCategoryPlayAndRecord;
            break;
        default:
            break;
    }
    if (avCategory) {
        <#statements#>
    }
}

- (NSMutableArray *)addTextQueue {
    if(!_addTextQueue){
        _addTextQueue = [[NSMutableArray alloc] init];
    }
    return _addTextQueue;
}

-(void)configureSDK{
    NSLog(@"TTS version info: %@", [BDSSpeechSynthesizer version]);
    [BDSSpeechSynthesizer setLogLevel:BDS_PUBLIC_LOG_VERBOSE];
    [[BDSSpeechSynthesizer sharedInstance] setSynthesizerDelegate:self];
    [self configureOnlineTTS];
}

-(void)configureOnlineTTS{
    
    [[BDSSpeechSynthesizer sharedInstance] setApiKey:self.apiKey withSecretKey:self.secretKey];
    [[BDSSpeechSynthesizer sharedInstance] setSynthParam:@(BDS_SYNTHESIZER_SPEAKER_FEMALE) forKey:BDS_SYNTHESIZER_PARAM_SPEAKER];
    [[BDSSpeechSynthesizer sharedInstance] setSynthParam:@(7) forKey:BDS_SYNTHESIZER_PARAM_SPEED];
}

-(void)configureOfflineTTS{
    
}

-(void)logTitle:(NSString *)title message:(NSString *)message {
    NSLog(@"error: %@ detail: %@",title,message);
}

-(void)displayError:(NSError*)error withTitle:(NSString*)title{
    NSString* errMessage = error.localizedDescription;
    NSLog(@"error: %@ detail: %@",title,errMessage);
}

-(void)updateSynthProgress{
//    [self.SynthesizeTextProgressView setText:nil];
    NSMutableAttributedString *str = [[NSMutableAttributedString alloc] init];
    if(displayAllSentences){
        for(NSDictionary* contentDict in self.synthesisTexts){
            [str appendAttributedString:[contentDict objectForKey:@"TEXT"]];
        }
    }
    else{
        if(self.synthesisTexts.count > 0){
            NSDictionary* contentDict = [self.synthesisTexts objectAtIndex:0];
            [str appendAttributedString:[contentDict objectForKey:@"TEXT"]];
        }
    }
//    [self.SynthesizeTextProgressView setAttributedText:str];
}

-(void)addFileTextLoop
{
    if(self.addTextQueue.count > 0){
        NSAttributedString* string = [[NSAttributedString alloc] initWithString:[self.addTextQueue objectAtIndex:0]];
        [self.addTextQueue removeObjectAtIndex:0];
        NSInteger sentenceID;
        NSError* err = nil;
        if(isSpeak)
            sentenceID = [[BDSSpeechSynthesizer sharedInstance] speakSentence:[string string] withError:&err];
        else
            sentenceID = [[BDSSpeechSynthesizer sharedInstance] synthesizeSentence:[string string] withError:&err];
        if(err == nil){
            NSMutableDictionary *addedString = [[NSMutableDictionary alloc] initWithObjects:@[string, [NSNumber numberWithInteger:sentenceID], [NSNumber numberWithInteger:0], [NSNumber numberWithInteger:0]] forKeys:@[@"TEXT", @"ID", @"SPEAK_LEN", @"SYNTH_LEN"]];
            [self.synthesisTexts addObject:addedString];
            [self updateSynthProgress];
            if(self.synthesisTexts.count == 1){
                self.cancelEnable = YES;
                self.pauseOrResumeEnable = YES;
            }
        }
        else{
            [self displayError:err withTitle:@"Add sentence Error"];
            [self.addTextQueue removeAllObjects];
        }
    }
    if(self.addTextQueue.count > 0){
        [self performSelector:@selector(addFileTextLoop) withObject:nil afterDelay:0.2];
    }
}

- (void)synthesizeFile:(NSString *)text_file {
    if(textFromFile){
        if(text_file == nil){
    
            NSString *domain = @"cn.histron";
            NSString *desc = [NSString stringWithFormat:@"Couldn't find text file \"%@\" from mainBundle",text_file];//NSLocalizedString国际化
            NSDictionary *userInfo = @{NSLocalizedDescriptionKey:desc};
            NSError *error = [NSError errorWithDomain:domain code:500 userInfo:userInfo];
            [self displayError:error withTitle:@"File not found"];
            return;
        }
        int total = 0;
        TTDFileReader* reader = [[TTDFileReader alloc] initWithFilePath:text_file];
        
        NSString *line = @"";
        NSStringEncoding gbkEncoding = CFStringConvertEncodingToNSStringEncoding(/*kCFStringEncodingUTF8*/kCFStringEncodingGB_18030_2000);
        while ( (line = [reader readLineWithEncoding:gbkEncoding]) ) {
            total++;
        }
        total = total*4;
        int added = 1;
        //        for(int i = 0;i < 4; i++){
        reader = [[TTDFileReader alloc] initWithFilePath:text_file];
        
        //            NSString *line = @"";
        //            NSStringEncoding gbkEncoding = CFStringConvertEncodingToNSStringEncoding(/*kCFStringEncodingUTF8*/kCFStringEncodingGB_18030_2000);
        while ( (line = [reader readLineWithEncoding:gbkEncoding]) ) {
            [self.addTextQueue addObject:[NSString stringWithFormat:@"Text %d of %d: %@", added, total, line]];
            added++;
        }
        //        }
        [self performSelector:@selector(addFileTextLoop) withObject:nil afterDelay:0.2];
    }
}



- (void)synthesizeText:(NSString *)text
{
    if(textFromFile)return;
    //[self.SynthesizeButton setEnabled:NO];
    NSAttributedString* string = [[NSAttributedString alloc] initWithString:text];
    NSInteger sentenceID;
    NSError* err = nil;
    if(isSpeak)
        sentenceID = [[BDSSpeechSynthesizer sharedInstance] speakSentence:[string string] withError:&err];
    else
        sentenceID = [[BDSSpeechSynthesizer sharedInstance] synthesizeSentence:[string string] withError:&err];
    if(err == nil){
        NSMutableDictionary *addedString = [[NSMutableDictionary alloc] initWithObjects:@[string, [NSNumber numberWithInteger:sentenceID], [NSNumber numberWithInteger:0], [NSNumber numberWithInteger:0]] forKeys:@[@"TEXT", @"ID", @"SPEAK_LEN", @"SYNTH_LEN"]];
        [self.synthesisTexts addObject:addedString];
        [self updateSynthProgress];
        if(self.synthesisTexts.count == 1){
            self.cancelEnable = YES;
            self.pauseOrResumeEnable = YES;
        }
    }
    else{
        [self displayError:err withTitle:@"Add sentence Error"];
    }
}
- (void)pauseOrResume
{
    if([[BDSSpeechSynthesizer sharedInstance] synthesizerStatus] == BDS_SYNTHESIZER_STATUS_PAUSED){
        [[BDSSpeechSynthesizer sharedInstance] resume];
    }else if([[BDSSpeechSynthesizer sharedInstance] synthesizerStatus] == BDS_SYNTHESIZER_STATUS_WORKING){
        [[BDSSpeechSynthesizer sharedInstance] pause];
    }else{
        [self logTitle:@"Error" message:@"Synthesis doesn't seem to be running so can't pause or resume..."];
        self.cancelEnable = NO;
        self.pauseOrResumeEnable = NO;
    }
    
}
- (void)cancel{
    if(self.addTextQueue){
        [self.addTextQueue removeAllObjects];
    }
    [[BDSSpeechSynthesizer sharedInstance] cancel];
    [self.synthesisTexts removeAllObjects];
    [self updateSynthProgress];
    self.cancelEnable = NO;
    self.pauseOrResumeEnable = NO;
}
- (void)releaseInstance{
    [self cancel];
    [BDSSpeechSynthesizer releaseInstance];
}


-(void)refreshAfterProgressUpdate:(NSMutableDictionary*)updatedSentence{
    NSString* totalText = [((NSAttributedString*)[updatedSentence objectForKey:@"TEXT"]) string];
    NSInteger readOffset = [[updatedSentence objectForKey:@"SPEAK_LEN"] integerValue];
    NSInteger synthOffset = [[updatedSentence objectForKey:@"SYNTH_LEN"] integerValue];
    
    NSLog(@"UPDATE PROGRESS: ReadLen: %ld, SynthLen: %ld, TotalLen: %ld", readOffset, synthOffset, totalText.length);
    
    NSRange readRange = NSMakeRange(0, readOffset);
    NSRange synthRange = NSMakeRange(readOffset, synthOffset-readOffset);
    NSRange unprocessedRange = NSMakeRange(synthOffset, totalText.length-synthOffset);
    
    NSString* readText = [totalText substringWithRange: readRange];
    NSString* synthesizeSentenceText = [totalText substringWithRange: synthRange];
    NSString* unProcessedText = [totalText substringWithRange: unprocessedRange];
    
    NSLog(@"readText: %@",readText);
    NSLog(@"synthesizeSentenceText: %@",synthesizeSentenceText);
    NSLog(@"unProcessedText: %@",unProcessedText);

//    [updatedSentence setObject:allMessage forKey:@"TEXT"];
//    [self updateSynthProgress];
}

#pragma mark - implement BDSSpeechSynthesizerDelegate
- (void)synthesizerStartWorkingSentence:(NSInteger)SynthesizeSentence{
    NSLog(@"Did start synth %ld", SynthesizeSentence);
    self.cancelEnable = YES;
    self.pauseOrResumeEnable = YES;
}

- (void)synthesizerFinishWorkingSentence:(NSInteger)SynthesizeSentence{
    NSLog(@"Did finish synth, %ld", SynthesizeSentence);
    if(!isSpeak){
        if(self.synthesisTexts.count > 0 &&
           SynthesizeSentence == [[[self.synthesisTexts objectAtIndex:0] objectForKey:@"ID"] integerValue]){
            [self.synthesisTexts removeObjectAtIndex:0];
            [self updateSynthProgress];
        }
        else{
            NSLog(@"Sentence ID mismatch??? received ID: %ld\nKnown sentences:", (long)SynthesizeSentence);
            for(NSDictionary* dict in self.synthesisTexts){
                NSLog(@"ID: %ld Text:\"%@\"", [[dict objectForKey:@"ID"] integerValue], [((NSAttributedString*)[dict objectForKey:@"TEXT"]) string]);
            }
        }
        if(self.synthesisTexts.count == 0){
            self.cancelEnable = NO;
            self.pauseOrResumeEnable = NO;
        }
    }
}

- (void)synthesizerSpeechStartSentence:(NSInteger)SpeakSentence{
    NSLog(@"Did start speak %ld", SpeakSentence);
}

- (void)synthesizerSpeechEndSentence:(NSInteger)SpeakSentence{
    NSLog(@"Did end speak %ld", SpeakSentence);
    if(self.synthesisTexts.count > 0 &&
       SpeakSentence == [[[self.synthesisTexts objectAtIndex:0] objectForKey:@"ID"] integerValue]){
        [self.synthesisTexts removeObjectAtIndex:0];
        [self updateSynthProgress];
    }
    else{
        NSLog(@"Sentence ID mismatch??? received ID: %ld\nKnown sentences:", (long)SpeakSentence);
        for(NSDictionary* dict in self.synthesisTexts){
            NSLog(@"ID: %ld Text:\"%@\"", [[dict objectForKey:@"ID"] integerValue], [((NSAttributedString*)[dict objectForKey:@"TEXT"]) string]);
        }
    }
    if(self.synthesisTexts.count == 0){
        self.cancelEnable = NO;
        self.pauseOrResumeEnable = NO;
    }
}

- (void)synthesizerNewDataArrived:(NSData *)newData
                       DataFormat:(BDSAudioFormat)fmt
                   characterCount:(int)newLength
                   sentenceNumber:(NSInteger)SynthesizeSentence{
    NSMutableDictionary* sentenceDict = nil;
    for(NSMutableDictionary *dict in self.synthesisTexts){
        if([[dict objectForKey:@"ID"] integerValue] == SynthesizeSentence){
            sentenceDict = dict;
            break;
        }
    }
    if(sentenceDict == nil){
        NSLog(@"Sentence ID mismatch??? received ID: %ld\nKnown sentences:", (long)SynthesizeSentence);
        for(NSDictionary* dict in self.synthesisTexts){
            NSLog(@"ID: %ld Text:\"%@\"", [[dict objectForKey:@"ID"] integerValue], [((NSAttributedString*)[dict objectForKey:@"TEXT"]) string]);
        }
        return;
    }
    [sentenceDict setObject:[NSNumber numberWithInteger:newLength] forKey:@"SYNTH_LEN"];
    [self refreshAfterProgressUpdate:sentenceDict];
}

- (void)synthesizerTextSpeakLengthChanged:(int)newLength
                           sentenceNumber:(NSInteger)SpeakSentence{
    NSLog(@"SpeakLen %ld, %d", SpeakSentence, newLength);
    NSMutableDictionary* sentenceDict = nil;
    for(NSMutableDictionary *dict in self.synthesisTexts){
        if([[dict objectForKey:@"ID"] integerValue] == SpeakSentence){
            sentenceDict = dict;
            break;
        }
    }
    if(sentenceDict == nil){
        NSLog(@"Sentence ID mismatch??? received ID: %ld\nKnown sentences:", (long)SpeakSentence);
        for(NSDictionary* dict in self.synthesisTexts){
            NSLog(@"ID: %ld Text:\"%@\"", [[dict objectForKey:@"ID"] integerValue], [((NSAttributedString*)[dict objectForKey:@"TEXT"]) string]);
        }
        return;
    }
    [sentenceDict setObject:[NSNumber numberWithInteger:newLength] forKey:@"SPEAK_LEN"];
    [self refreshAfterProgressUpdate:sentenceDict];
}

- (void)synthesizerdidPause{
    [self logTitle:@"synthesizer did pause" message:@""];
}

- (void)synthesizerResumed{
    NSLog(@"Did resume");
    [self logTitle:@"synthesizer Resumed" message:@""];

}

- (void)synthesizerCanceled{
    NSLog(@"Did cancel");
}


- (void)synthesizerErrorOccurred:(NSError *)error
                        speaking:(NSInteger)SpeakSentence
                    synthesizing:(NSInteger)SynthesizeSentence{
    NSLog(@"Did error %ld, %ld", SpeakSentence, SynthesizeSentence);
    if(self.addTextQueue){
        [self.addTextQueue removeAllObjects];
    }
    self.cancelEnable = NO;
    self.pauseOrResumeEnable = NO;
    [self.synthesisTexts removeAllObjects];
    [self updateSynthProgress];
    [[BDSSpeechSynthesizer sharedInstance] cancel];
    [self displayError:error withTitle:@"Synthesis failed"];
}

@end