問題
iOS15之後,推送多條語音會產生多條橫幅,對於動態金額語音,多條橫幅是不可取的
解決方案
- 做版本管理,iOS15以上,用新的解決方案實現,iOS15以下還是沿用舊的推送方案
/// !!!!: 推送語音播報總控制邏輯
/// @param sourceURLsArr mp3源文件數組
/// @param bestAttemptContent
/// @param completed
-(void)pushVoiceNotificationWithWithSourceURLs:(NSArray *)sourceURLsArr
bestAttemptContent:(UNMutableNotificationContent *)bestAttemptContent
completed:(XSNotificationPushCompleted)completed
{
if (@available(iOS 15.0, *)) {
// 合併音頻文件生成新的音頻
[self mergeAVAssetWithSourceURLs:sourceURLsArr completed:^(NSString *soundName, NSURL *soundsFileURL) {
if (!soundName) {
NSLog(@"聲音生成失敗!");
completed();
return;
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000
if (@available(iOS 15.0, *)) {
bestAttemptContent.interruptionLevel = UNNotificationInterruptionLevelTimeSensitive;
}
#endif
UNNotificationSound * sound = [UNNotificationSound soundNamed:soundName];
bestAttemptContent.sound = sound;
completed();
}];
}
else//iOS15以下,改用原來舊方式實現
{
[self pushLocalNotificationIniOS14ToApp:0 withArray:sourceURLsArr completed:^{
completed();
}];
}
}
- 新方案裏面,通過NSFileManager把輸出音頻保存在【AppGroup】的/Library/Sounds/裏面,坑點就是,AVAssetExportSession的輸出路徑必須要保證文件夾存在,不然會提示操作有誤,當時直接通過contentsOfDirectoryAtPath來生成兩個文件夾,結果不行, 必須要逐個生成,並且要留意生成的文件後綴要符合輸出格式要求
///在AppGroup中合併音頻
- (void)mergeAVAssetWithSourceURLs:(NSArray *)sourceURLsArr completed:(void (^)(NSString * soundName,NSURL * soundsFileURL)) completed{
//創建音頻軌道,並獲取多個音頻素材的軌道
AVMutableComposition *composition = [AVMutableComposition composition];
//音頻插入的開始時間,用於記錄每次添加音頻文件的開始時間
__block CMTime beginTime = kCMTimeZero;
[sourceURLsArr enumerateObjectsUsingBlock:^(id _Nonnull audioFileURL, NSUInteger idx, BOOL * _Nonnull stop) {
//獲取音頻素材
AVURLAsset *audioAsset1 = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:audioFileURL]];
//音頻軌道
AVMutableCompositionTrack *audioTrack1 = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:0];
//獲取音頻素材軌道
AVAssetTrack *audioAssetTrack1 = [[audioAsset1 tracksWithMediaType:AVMediaTypeAudio] firstObject];
//音頻合併- 插入音軌文件
[audioTrack1 insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioAsset1.duration) ofTrack:audioAssetTrack1 atTime:beginTime error:nil];
// 記錄尾部時間
beginTime = CMTimeAdd(beginTime, audioAsset1.duration);
}];
//用動態日期會佔用空間
// NSDateFormatter *formater = [[NSDateFormatter alloc] init];
// [formater setDateFormat:@"yyyy-MM-dd-HH:mm:ss-SSS"];
// NSString * timeFromDateStr = [formater stringFromDate:[NSDate date]];
// NSString *outPutFilePath = [NSHomeDirectory() stringByAppendingFormat:@"/tmp/sound-%@.mp4", timeFromDateStr];
NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:kAppGroupID];
// NSURL * soundsURL = [groupURL URLByAppendingPathComponent:@"/Library/Sounds/" isDirectory:YES];
//建立文件夾
NSURL * soundsURL = [groupURL URLByAppendingPathComponent:@"Library/" isDirectory:YES];
if (![[NSFileManager defaultManager] contentsOfDirectoryAtPath:soundsURL.path error:nil]) {
[[NSFileManager defaultManager] createDirectoryAtPath:soundsURL.path withIntermediateDirectories:YES attributes:nil error:nil];
}
//建立文件夾
NSURL * soundsURL2 = [groupURL URLByAppendingPathComponent:@"Library/Sounds/" isDirectory:YES];
if (![[NSFileManager defaultManager] contentsOfDirectoryAtPath:soundsURL2.path error:nil]) {
[[NSFileManager defaultManager] createDirectoryAtPath:soundsURL2.path withIntermediateDirectories:YES attributes:nil error:nil];
}
// 新建文件名,如果存在就刪除舊的
NSString * soundName = [NSString stringWithFormat:@"sound.m4a"];
NSString *outPutFilePath = [NSString stringWithFormat:@"Library/Sounds/%@", soundName];
NSURL * soundsFileURL = [groupURL URLByAppendingPathComponent:outPutFilePath isDirectory:NO];
// NSString * filePath = soundsURL.absoluteString;
if ([[NSFileManager defaultManager] fileExistsAtPath:soundsFileURL.path]) {
[[NSFileManager defaultManager] removeItemAtPath:soundsFileURL.path error:nil];
}
//導出合併後的音頻文件
//音頻文件目前只找到支持m4a 類型的
AVAssetExportSession *session = [[AVAssetExportSession alloc]initWithAsset:composition presetName:AVAssetExportPresetAppleM4A];
// 音頻文件輸出
session.outputURL = soundsFileURL;
session.outputFileType = AVFileTypeAppleM4A; //與上述的`present`相對應
session.shouldOptimizeForNetworkUse = YES; //優化網絡
[session exportAsynchronouslyWithCompletionHandler:^{
if (session.status == AVAssetExportSessionStatusCompleted) {
NSLog(@"合併成功----%@", outPutFilePath);
if (completed) {
completed(soundName,soundsFileURL);
}
} else {
// 其他情況, 具體請看這裏`AVAssetExportSessionStatus`.
NSLog(@"合併失敗----%ld", (long)session.status);
if (completed) {
completed(nil,nil);
}
}
}];
}
- iOS15以下方案不變,通過循環遞歸推送多條語音信息來實現
////循環調用本地通知,播放音頻文件
-(void)pushLocalNotificationIniOS14ToApp:(NSInteger)index withArray:(NSArray *)tmparray completed:(XSNotificationPushCompleted)completed{
__block NSInteger tmpindex = index;
if(tmpindex < [tmparray count]){
NSString *audioFileURL = tmparray[tmpindex];
NSString * mp3Name = [audioFileURL lastPathComponent];
AVURLAsset *audioAsset=[AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:audioFileURL] options:nil];
//獲取本地mpe3e文件時長
CMTime audioDuration = audioAsset.duration;
float audioDurationSeconds = CMTimeGetSeconds(audioDuration);
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; //標題
content.sound = [UNNotificationSound soundNamed:[NSString stringWithFormat:@"%@",mp3Name]];
content.body = @"";
// repeats,是否重複,如果重複的話時間必須大於60s,要不會報錯
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:0.01 repeats:NO];
/* */
//添加通知的標識符,可以用於移除,更新等搡作
NSString * identifier = [[NSUUID UUID] UUIDString];
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger];
[center addNotificationRequest:request withCompletionHandler:^(NSError *_Nullable error) {
//第一條推送成功後,遞歸執行
float time = audioDurationSeconds+0.1;
if (![mp3Name containsString:@"pre"]) {
time = 0.4;
}
tmpindex = tmpindex+1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, time * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self pushLocalNotificationIniOS14ToApp:tmpindex withArray:tmparray completed:completed];
});
}];
}else{
completed();
}
}
參考:
https://www.jianshu.com/p/a01c0b59b9c4
https://juejin.cn/post/7026639897289031687