iOS15 推送动态语音播报解决方案

2021-12-29 10:51:31 浏览数 (1)

问题

iOS15之后,推送多条语音会产生多条横幅,对于动态金额语音,多条横幅是不可取的

解决方案

代码语言:javascript复制
做版本管理,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();
        }];
    }
}
代码语言:javascript复制
新方案里面,通过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

0 人点赞