iOS13推送語音播報

目前市面上很多支付APP都需要在收款成功后,進(jìn)行語音提示,例如收錢吧,微信队萤,支付寶等!公司App現(xiàn)在也需要加入這個功能,這里記錄下踩過的坑

該功能需要用到 蘋果的 Notification Service Extension 這個是iOS10.0推出的冯吓。https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension

實(shí)現(xiàn)該功能

一,添加 Notification Service Extension

target1.png
target2.png
target3.png

創(chuàng)建之后程序內(nèi)會出現(xiàn) NotificationService.h ,NotificationService.m 文件


target4.png

二拴签,然后就是發(fā)送推送消息 纹份,以極光推送為例

(iOS 10 新增的 Notification Service Extension 功能苟跪,用 mutable-content 字段來控制。 若使用極光的 Web 控制臺蔓涧,需勾選 “可選設(shè)置”中 mutable-content 選項件已;若使用 RESTFul API 需設(shè)置 mutable-content 字段為 true。)

三元暴,攔截推送信息篷扩,播放語音

5.png

設(shè)置好后我們每次發(fā)送推送,都會走到NotificationService中的這個回調(diào)茉盏,獲取到推送中附帶的信息(ps:如果發(fā)現(xiàn)沒走回調(diào)鉴未,請對照上一步,查看 極光控制臺mutable-content 是否勾選鸠姨,后臺或其他方式推送要此字段設(shè)置為1)铜秆;

(1)ios12以前

ios12以前,這個功能還是比較好做的讶迁,收到推送后连茧,調(diào)用語音庫AVSpeechSynthesisVoice讀出來就可以,

av= [[AVSpeechSynthesizer alloc]init];
av.delegate=self;//掛上代理
AVSpeechSynthesisVoice*voice = [AVSpeechSynthesisVoicevoiceWithLanguage:@"zh-CN"];//設(shè)置發(fā)音,這是中文普通話
AVSpeechUtterance*utterance = [[AVSpeechUtterance   alloc]initWithString:@"需要播報的文字"];//需要轉(zhuǎn)換的文字
utterance.rate=0.6;// 設(shè)置語速梅屉,范圍0-1值纱,注意0最慢,1最快坯汤;
utterance.voice= voice;
[avspeakUtterance:utterance];//開始

或者內(nèi)置幾段語音進(jìn)行合成后再進(jìn)行播放

//MARK:音頻憑借
- (void)audioMergeClick{
//1.獲取本地音頻素材
    NSString *audioPath1 = [[NSBundle mainBundle]pathForResource:@"一" ofType:@"mp3"];
    NSString *audioPath2 = [[NSBundle mainBundle]pathForResource:@"元" ofType:@"mp3"];
    AVURLAsset *audioAsset1 = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:audioPath1]];
    AVURLAsset *audioAsset2 = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:audioPath2]];
//2.創(chuàng)建兩個音頻軌道,并獲取兩個音頻素材的軌道
    AVMutableComposition *composition = [AVMutableComposition composition];
    //音頻軌道
    AVMutableCompositionTrack *audioTrack1 = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:0];
    AVMutableCompositionTrack *audioTrack2 = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:0];
    //獲取音頻素材軌道
    AVAssetTrack *audioAssetTrack1 = [[audioAsset1 tracksWithMediaType:AVMediaTypeAudio] firstObject];
    AVAssetTrack *audioAssetTrack2 = [[audioAsset2 tracksWithMediaType:AVMediaTypeAudio]firstObject];
//3.將兩段音頻插入音軌文件,進(jìn)行合并
    //音頻合并- 插入音軌文件
    // `startTime`參數(shù)要設(shè)置為第一段音頻的時長虐唠,即`audioAsset1.duration`, 表示將第二段音頻插入到第一段音頻的尾部。

    [audioTrack1 insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioAsset1.duration) ofTrack:audioAssetTrack1 atTime:kCMTimeZero error:nil];
    [audioTrack2 insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioAsset2.duration) ofTrack:audioAssetTrack2 atTime:audioAsset1.duration error:nil];
//4. 導(dǎo)出合并后的音頻文件
    //`presetName`要和之后的`session.outputFileType`相對應(yīng)
    //音頻文件目前只找到支持m4a 類型的
    AVAssetExportSession *session = [[AVAssetExportSession alloc]initWithAsset:composition presetName:AVAssetExportPresetAppleM4A];
    
    NSString *outPutFilePath = [[self.filePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"xindong.m4a"];
    
    if ([[NSFileManager defaultManager] fileExistsAtPath:outPutFilePath]) {
        [[NSFileManager defaultManager] removeItemAtPath:outPutFilePath error:nil];
    }
    // 查看當(dāng)前session支持的fileType類型
    NSLog(@"---%@",[session supportedFileTypes]);
    session.outputURL = [NSURL fileURLWithPath:self.filePath];
    session.outputFileType = AVFileTypeAppleM4A; //與上述的`present`相對應(yīng)
    session.shouldOptimizeForNetworkUse = YES;   //優(yōu)化網(wǎng)絡(luò)
    [session exportAsynchronouslyWithCompletionHandler:^{
        if (session.status == AVAssetExportSessionStatusCompleted) {
            NSLog(@"合并成功----%@", outPutFilePath);
            _audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:outPutFilePath] error:nil];
            [_audioPlayer play];
        } else {
            // 其他情況, 具體請看這里`AVAssetExportSessionStatus`.
        }
    }];
    
}


- (NSString *)filePath {
    if (!_filePath) {
        _filePath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject];
        NSString *folderName = [_filePath stringByAppendingPathComponent:@"MergeAudio"];
        BOOL isCreateSuccess = [kFileManager createDirectoryAtPath:folderName withIntermediateDirectories:YES attributes:nil error:nil];
        if (isCreateSuccess) _filePath = [folderName stringByAppendingPathComponent:@"xindong.m4a"];
    }
    return _filePath;
}

該方法可以內(nèi)置1-10惰聂,點(diǎn)疆偿、元等單音頻后拼接成需要的語音,然后利用
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:outPutFilePath] error:nil];
[_audioPlayer play];
播放出來
具體合成方法參考
http://www.reibang.com/p/a739c200b3c8
http://www.reibang.com/p/3e357e3129b8
或者最簡單的方案搓幌,集成訊飛杆故,百度等三方合成語音

(2)iOS13播報

在iOS12.1發(fā)布后,上述方案已經(jīng)不行了溉愁,

據(jù)說蘋果給出的解釋是 Notification Service Extension是為了豐富推送體驗处铛,主要是為了富文本推送圖片的處理,所以在Notification Service Extension中禁用了play播放器相關(guān)拐揭!有需要的可以使用官方的sound字段播放自定義的語音

關(guān)于sound字段

sound字段是官方推送的一個默認(rèn)字段撤蟆,蘋果官方文檔說明可以將音頻放到工程主目錄,或者Libray/Sounds堂污,在推送到達(dá)時家肯,系統(tǒng)將根據(jù)sound字段在目錄中找到對應(yīng)音頻播放,支持的格式aiff,caf,wav盟猖!


7.png

比如極光推送的控制臺就是這個字段

但是這就限制了讨衣,必須在打包之前就把語音放進(jìn)工程目錄!只能用固定的語音了式镐!
那么最笨的方案就是內(nèi)置一萬多條語音反镇,然后推送的時候直接讓后端用sound來指定播放的語音,但是在包的大小……

網(wǎng)上翻閱很久娘汞,后來發(fā)現(xiàn)愿险,sound除了播放工程主目錄和Library/Sounds,還可以播放AppGroup中Library/Sounds的音頻 那這就好辦了,我們可以在后臺合成价说,然后下載到AppGroup后修改sound字段進(jìn)行播放(前端合成到處到指定文件夾應(yīng)該也可以)

首先打開我們項目的AppGroup


image.png
image.png

打開后記得??辆亏,然后再打開Notification Service Extension 的AppGroup 也就是圖中名為PushDemo的的targets,也要同樣操作一遍


之后接到通知鳖目,解析出下載鏈接扮叨,下載完在本地修改sound字段,交由系統(tǒng)播報(應(yīng)該也可以本地拼接后合成到處到對應(yīng)文件夾领迈,筆者當(dāng)時沒有嘗試彻磁,各位可以自己嘗試)

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    // Modify the notification content here...
    self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
   
    // 這個info 內(nèi)容就是通知信息攜帶的數(shù)據(jù)碍沐,后面我們?nèi)≌Z音播報的文案,通知欄的title衷蜓,以及通知內(nèi)容都是從這個info字段中獲取
    NSDictionary *info = self.bestAttemptContent.userInfo;
    NSString * urlStr = [info objectForKey:@"soundUrl"];
    [self loadWavWithUrl:urlStr];
    
//    self.contentHandler(self.bestAttemptContent);
}
-(void)loadWavWithUrl:(NSString *)urlStr{
    NSLog(@"開始下載");
    NSURL *url = [NSURL URLWithString:urlStr];
       //默認(rèn)的congig
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    
    //session
    NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    self.task = [session downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (!error) {
            NSLog(@"下載完成");
            NSString * name = [NSString stringWithFormat:@"%u.wav",arc4random()%50000 ];
             //獲取保存文件的路徑
             NSString *path = self.filePath;
             //將url對應(yīng)的文件copy到指定的路徑

             NSFileManager *fileManager = [NSFileManager defaultManager];
             if(![fileManager fileExistsAtPath:path]){
                 [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
             }
             NSString * soundStr = [NSString stringWithFormat:@"%@",name];

             NSString *savePath = [path stringByAppendingPathComponent:[NSString stringWithFormat:@"%@",soundStr]];
             if ([fileManager fileExistsAtPath:savePath]) {
                 [fileManager removeItemAtPath:savePath error:nil];
                }
             NSURL *saveURL = [NSURL fileURLWithPath:savePath];
            
             NSError * saveError;
             // 文件移動到cache路徑中
             [[NSFileManager defaultManager] moveItemAtURL:location toURL:saveURL error:&saveError];
             if (!saveError)
             {
                 AVURLAsset *audioAsset=[AVURLAsset URLAssetWithURL:saveURL options:nil];
                 self.bestAttemptContent.sound = soundStr;
                 self.contentHandler(self.bestAttemptContent);
             }

        }else{
            
            NSLog(@"失敗");
        }
         
    }];
    
    //啟動下載任務(wù)
    [_task resume];
}
- (NSString *)filePath {
    if (_filePath) {
        return _filePath;
    }
    NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.jiutianyunzhu.BPMall"];
    NSString *groupPath = [groupURL path];

     _filePath = [groupPath stringByAppendingPathComponent:@"Library/Sounds"];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:_filePath]) {
        [fileManager createDirectoryAtPath:_filePath withIntermediateDirectories:NO attributes:nil error:nil];
    }
    return _filePath;
}

當(dāng)音頻下載處理完成后記得調(diào)用self.contentHandler(self.bestAttemptContent);
只有當(dāng)調(diào)用self.contentHandler(self.bestAttemptContent);之后累提,才會彈出頂部橫幅,并開始播報磁浇,橫幅消失時音頻會停止斋陪,實(shí)測橫幅時長大概6s!所以音頻需要處理控制在6s之內(nèi)置吓!

測試這種方案ios13播放沒用問題无虚,ios12上沒有正確播放,如果有好的修改方案衍锚,歡迎私信

需要注意的問題

1.網(wǎng)上大都說支持三種格式 aiff友题、caf以及wav,但實(shí)測也支持MP3格式
2.處理完成后一定要記得調(diào)用 self.contentHandler(self.bestAttemptContent);戴质,否則不會出現(xiàn)通知橫幅
3.下載失敗最好準(zhǔn)備一段默認(rèn)語音播報
4.多條推送同時到達(dá)問題度宦,可以寫個隊列,調(diào)用self.contentHandler(self.bestAttemptContent);后告匠,主動去阻塞線程一定的時長(音頻時長)戈抄,播放完成后記得刪除掉!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凫海,一起剝皮案震驚了整個濱河市呛凶,隨后出現(xiàn)的幾起案子男娄,更是在濱河造成了極大的恐慌行贪,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件模闲,死亡現(xiàn)場離奇詭異建瘫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)尸折,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門啰脚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人实夹,你說我怎么就攤上這事橄浓。” “怎么了亮航?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵荸实,是天一觀的道長。 經(jīng)常有香客問我缴淋,道長准给,這世上最難降的妖魔是什么泄朴? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮露氮,結(jié)果婚禮上祖灰,老公的妹妹穿的比我還像新娘。我一直安慰自己畔规,他們只是感情好局扶,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著油讯,像睡著了一般详民。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上陌兑,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天沈跨,我揣著相機(jī)與錄音,去河邊找鬼兔综。 笑死饿凛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的软驰。 我是一名探鬼主播涧窒,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼锭亏!你這毒婦竟也來了纠吴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤慧瘤,失蹤者是張志新(化名)和其女友劉穎戴已,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锅减,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡糖儡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了怔匣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片握联。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖每瞒,靈堂內(nèi)的尸體忽然破棺而出金闽,到底是詐尸還是另有隱情,我是刑警寧澤剿骨,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布代芜,位于F島的核電站,受9級特大地震影響懦砂,放射性物質(zhì)發(fā)生泄漏蜒犯。R本人自食惡果不足惜组橄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望罚随。 院中可真熱鬧玉工,春花似錦、人聲如沸淘菩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潮改。三九已至狭郑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汇在,已是汗流浹背翰萨。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留糕殉,地道東北人亩鬼。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像阿蝶,于是被迫代替她去往敵國和親雳锋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344